From 8a0ec5f8a72af29172505dd09057d15fbfab7392 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 11:37:44 +0000 Subject: [PATCH 01/59] Create ChatBoxScreen layout --- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 54 +++++++++++++++++++ .../ChatBox/SelectedClipEditorWrapper.cs | 48 +++++++++++++++++ .../ChatBox/SelectedClipMetadataEditor.cs | 23 ++++++++ .../ChatBox/SelectedClipModuleSelector.cs | 23 ++++++++ .../ChatBox/SelectedClipStateEditor.cs | 23 ++++++++ .../Graphics/ChatBox/TimelineEditor.cs | 23 ++++++++ .../Graphics/ChatBox/TimelineEditorWrapper.cs | 41 ++++++++++++++ .../ChatBox/TimelineMetadataEditor.cs | 23 ++++++++ VRCOSC.Game/Graphics/MainContent.cs | 2 + VRCOSC.Game/Graphics/TabBar/Tab.cs | 1 + VRCOSC.Game/Graphics/TabBar/TabSelector.cs | 1 + 11 files changed, 262 insertions(+) create mode 100644 VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs new file mode 100644 index 00000000..89bc8b23 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -0,0 +1,54 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class ChatBoxScreen : Container +{ + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ThemeManager.Current[ThemeAttribute.Light] + }, + new SelectedClipEditorWrapper + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding + { + Horizontal = 10, + Top = 10, + Bottom = 5 + } + }, + new TimelineEditorWrapper + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding + { + Horizontal = 10, + Top = 5, + Bottom = 10 + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs new file mode 100644 index 00000000..aa2cefb6 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs @@ -0,0 +1,48 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class SelectedClipEditorWrapper : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable?[] + { + new SelectedClipMetadataEditor + { + RelativeSizeAxes = Axes.Both + }, + null, + new SelectedClipModuleSelector + { + RelativeSizeAxes = Axes.Both + }, + null, + new SelectedClipStateEditor + { + RelativeSizeAxes = Axes.Both + } + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs new file mode 100644 index 00000000..8d3e9b09 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs @@ -0,0 +1,23 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class SelectedClipMetadataEditor : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs new file mode 100644 index 00000000..da1f0a72 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs @@ -0,0 +1,23 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class SelectedClipModuleSelector : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs new file mode 100644 index 00000000..76eb9269 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs @@ -0,0 +1,23 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class SelectedClipStateEditor : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs new file mode 100644 index 00000000..82493267 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs @@ -0,0 +1,23 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class TimelineEditor : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs new file mode 100644 index 00000000..ad557e9f --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs @@ -0,0 +1,41 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class TimelineEditorWrapper : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.175f), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable?[] + { + new TimelineMetadataEditor + { + RelativeSizeAxes = Axes.Both + }, + null, + new TimelineEditor + { + RelativeSizeAxes = Axes.Both + } + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs new file mode 100644 index 00000000..0addcffa --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs @@ -0,0 +1,23 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox; + +public partial class TimelineMetadataEditor : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } +} diff --git a/VRCOSC.Game/Graphics/MainContent.cs b/VRCOSC.Game/Graphics/MainContent.cs index 192ce39f..0ac92d01 100644 --- a/VRCOSC.Game/Graphics/MainContent.cs +++ b/VRCOSC.Game/Graphics/MainContent.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using VRCOSC.Game.Graphics.About; +using VRCOSC.Game.Graphics.ChatBox; using VRCOSC.Game.Graphics.ModuleListing; using VRCOSC.Game.Graphics.Router; using VRCOSC.Game.Graphics.Settings; @@ -51,6 +52,7 @@ private void load() Children = new Drawable[] { new ModuleListingScreen(), + new ChatBoxScreen(), new SettingsScreen(), new RouterScreen(), new AboutScreen() diff --git a/VRCOSC.Game/Graphics/TabBar/Tab.cs b/VRCOSC.Game/Graphics/TabBar/Tab.cs index 97841b24..22dfe81d 100644 --- a/VRCOSC.Game/Graphics/TabBar/Tab.cs +++ b/VRCOSC.Game/Graphics/TabBar/Tab.cs @@ -6,6 +6,7 @@ namespace VRCOSC.Game.Graphics.TabBar; public enum Tab { Modules, + ChatBox, Settings, Router, About diff --git a/VRCOSC.Game/Graphics/TabBar/TabSelector.cs b/VRCOSC.Game/Graphics/TabBar/TabSelector.cs index c95df942..5ab156bb 100644 --- a/VRCOSC.Game/Graphics/TabBar/TabSelector.cs +++ b/VRCOSC.Game/Graphics/TabBar/TabSelector.cs @@ -17,6 +17,7 @@ public sealed partial class TabSelector : Container private static readonly IReadOnlyDictionary icon_lookup = new Dictionary { { Tab.Modules, FontAwesome.Solid.ListUl }, + { Tab.ChatBox, FontAwesome.Solid.Get(62074) }, { Tab.Settings, FontAwesome.Solid.Cog }, { Tab.Router, FontAwesome.Solid.Get(61920) }, { Tab.About, FontAwesome.Solid.Info } From 7bf97316c2d5aecb0748de0b2e5a4b54d4581f80 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 12:06:39 +0000 Subject: [PATCH 02/59] Create basis for clip metadata editing --- VRCOSC.Game/ChatBox/Clip.cs | 24 ++++++++ VRCOSC.Game/ChatBox/ClipEvent.cs | 11 ++++ VRCOSC.Game/ChatBox/ClipState.cs | 12 ++++ VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 46 ++++++++------- .../Metadata/ClipMetadataToggle.cs | 55 +++++++++++++++++ .../SelectedClipEditorWrapper.cs | 19 ++++-- .../SelectedClipMetadataEditor.cs | 59 +++++++++++++++++++ .../SelectedClipModuleSelector.cs | 2 +- .../SelectedClipStateEditor.cs | 2 +- .../ChatBox/SelectedClipMetadataEditor.cs | 23 -------- .../ChatBox/{ => Timeline}/TimelineEditor.cs | 2 +- .../{ => Timeline}/TimelineEditorWrapper.cs | 4 +- .../{ => Timeline}/TimelineMetadataEditor.cs | 2 +- 13 files changed, 206 insertions(+), 55 deletions(-) create mode 100644 VRCOSC.Game/ChatBox/Clip.cs create mode 100644 VRCOSC.Game/ChatBox/ClipEvent.cs create mode 100644 VRCOSC.Game/ChatBox/ClipState.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs rename VRCOSC.Game/Graphics/ChatBox/{ => SelectedClip}/SelectedClipEditorWrapper.cs (70%) create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs rename VRCOSC.Game/Graphics/ChatBox/{ => SelectedClip}/SelectedClipModuleSelector.cs (91%) rename VRCOSC.Game/Graphics/ChatBox/{ => SelectedClip}/SelectedClipStateEditor.cs (91%) delete mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs rename VRCOSC.Game/Graphics/ChatBox/{ => Timeline}/TimelineEditor.cs (91%) rename VRCOSC.Game/Graphics/ChatBox/{ => Timeline}/TimelineEditorWrapper.cs (90%) rename VRCOSC.Game/Graphics/ChatBox/{ => Timeline}/TimelineMetadataEditor.cs (91%) diff --git a/VRCOSC.Game/ChatBox/Clip.cs b/VRCOSC.Game/ChatBox/Clip.cs new file mode 100644 index 00000000..f9863e97 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Clip.cs @@ -0,0 +1,24 @@ +// 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 osu.Framework.Bindables; + +namespace VRCOSC.Game.ChatBox; + +/// +/// Represents a timespan that contains all information the ChatBox will need for displaying +/// +public class Clip +{ + public readonly BindableBool Enabled = new(true); + public readonly Bindable Name = new("New Clip"); + public readonly List AssociatedModules = new(); + public readonly Dictionary States = new(); + public readonly Dictionary Events = new(); + + public DateTimeOffset Start { get; private set; } + public DateTimeOffset End { get; private set; } + public TimeSpan Length => End - Start; +} diff --git a/VRCOSC.Game/ChatBox/ClipEvent.cs b/VRCOSC.Game/ChatBox/ClipEvent.cs new file mode 100644 index 00000000..6cf87bea --- /dev/null +++ b/VRCOSC.Game/ChatBox/ClipEvent.cs @@ -0,0 +1,11 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; + +namespace VRCOSC.Game.ChatBox; + +public class ClipEvent : ClipState +{ + public TimeSpan Time = TimeSpan.Zero; +} diff --git a/VRCOSC.Game/ChatBox/ClipState.cs b/VRCOSC.Game/ChatBox/ClipState.cs new file mode 100644 index 00000000..dfd3fbd9 --- /dev/null +++ b/VRCOSC.Game/ChatBox/ClipState.cs @@ -0,0 +1,12 @@ +// 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.Bindables; + +namespace VRCOSC.Game.ChatBox; + +public class ClipState +{ + public string Format = string.Empty; + public BindableBool Enabled = new(); +} diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 89bc8b23..004287e3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -23,30 +23,34 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = ThemeManager.Current[ThemeAttribute.Light] }, - new SelectedClipEditorWrapper + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding + Padding = new MarginPadding(10), + Children = new Drawable[] { - Horizontal = 10, - Top = 10, - Bottom = 5 - } - }, - new TimelineEditorWrapper - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding - { - Horizontal = 10, - Top = 5, - Bottom = 10 + new SelectedClip.SelectedClipEditorWrapper + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding + { + Bottom = 2.5f + } + }, + new Timeline.TimelineEditorWrapper + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding + { + Top = 2.5f, + } + } } } }; diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs new file mode 100644 index 00000000..68d3c89c --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs @@ -0,0 +1,55 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.UI.Button; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; + +public partial class ClipMetadataToggle : Container +{ + public required string Label { get; init; } + public required BindableBool State { get; init; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 25), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Child = new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = (BindableBool)State.GetBoundCopy() + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs similarity index 70% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs rename to VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index aa2cefb6..b98a1b3b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -2,26 +2,33 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using VRCOSC.Game.ChatBox; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipEditorWrapper : Container { + [Cached] + private Bindable selectedClip { get; set; } = new(); + [BackgroundDependencyLoader] private void load() { + selectedClip.Value = new Clip(); + Child = new GridContainer { RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 10), + new Dimension(GridSizeMode.Absolute, 5), new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension() }, Content = new[] { @@ -29,7 +36,9 @@ private void load() { new SelectedClipMetadataEditor { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 }, null, new SelectedClipModuleSelector diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs new file mode 100644 index 00000000..e39a810c --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -0,0 +1,59 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class SelectedClipMetadataEditor : Container +{ + [Resolved] + private Bindable selectedClip { get; set; } = null!; + + private FillFlowContainer metadataFlow = null!; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both + }, + metadataFlow = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + } + }; + + selectedClip.BindValueChanged(e => onSelectedClipChange(e.NewValue), true); + } + + private void onSelectedClipChange(Clip? clip) + { + metadataFlow.Clear(); + + if (clip is null) return; + + metadataFlow.Add(new ClipMetadataToggle + { + Label = "Enabled", + State = clip.Enabled + }); + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs similarity index 91% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs rename to VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index da1f0a72..d43bd28d 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipModuleSelector : Container { diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs similarity index 91% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs rename to VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs index 76eb9269..30ea6dc3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClipStateEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipStateEditor : Container { diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs deleted file mode 100644 index 8d3e9b09..00000000 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClipMetadataEditor.cs +++ /dev/null @@ -1,23 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; - -namespace VRCOSC.Game.Graphics.ChatBox; - -public partial class SelectedClipMetadataEditor : Container -{ - [BackgroundDependencyLoader] - private void load() - { - Child = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }; - } -} diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs similarity index 91% rename from VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs rename to VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 82493267..c890466c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; public partial class TimelineEditor : Container { diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs similarity index 90% rename from VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs rename to VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs index ad557e9f..693a5cd3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/TimelineEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; public partial class TimelineEditorWrapper : Container { @@ -18,7 +18,7 @@ private void load() ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.175f), - new Dimension(GridSizeMode.Absolute, 10), + new Dimension(GridSizeMode.Absolute, 5), new Dimension(), }, Content = new[] diff --git a/VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs similarity index 91% rename from VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs rename to VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index 0addcffa..af316669 100644 --- a/VRCOSC.Game/Graphics/ChatBox/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace VRCOSC.Game.Graphics.ChatBox; +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; public partial class TimelineMetadataEditor : Container { From 5a2f20d7b0e77fe2646f4192375eda5a7df9cf96 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 12:16:16 +0000 Subject: [PATCH 03/59] Show empty graphics when no clip has been selected --- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 7 ++ .../SelectedClip/SelectedClipEditorWrapper.cs | 100 +++++++++++++----- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 004287e3..ddf8e5b2 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -2,18 +2,25 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox; public partial class ChatBoxScreen : Container { + [Cached] + private Bindable selectedClip { get; set; } = new(); + [BackgroundDependencyLoader] private void load() { + selectedClip.Value = new Clip(); + RelativeSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index b98a1b3b..8a4ee8ef 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -5,53 +5,99 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipEditorWrapper : Container { - [Cached] - private Bindable selectedClip { get; set; } = new(); + [Resolved] + private Bindable selectedClip { get; set; } = null!; + + private Container noClipContent = null!; + private GridContainer gridContent = null!; [BackgroundDependencyLoader] private void load() { - selectedClip.Value = new Clip(); - - Child = new GridContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + noClipContent = new Container { - new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension() - }, - Content = new[] - { - new Drawable?[] + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - new SelectedClipMetadataEditor - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 - }, - null, - new SelectedClipModuleSelector + new Box { + Colour = ThemeManager.Current[ThemeAttribute.Dark], RelativeSizeAxes = Axes.Both }, - null, - new SelectedClipStateEditor + new SpriteText { - RelativeSizeAxes = Axes.Both + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = FrameworkFont.Regular.With(size: 40), + Text = "Select a clip to edit" + } + } + }, + gridContent = new GridContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension() + }, + Content = new[] + { + new Drawable?[] + { + new SelectedClipMetadataEditor + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 + }, + null, + new SelectedClipModuleSelector + { + RelativeSizeAxes = Axes.Both + }, + null, + new SelectedClipStateEditor + { + RelativeSizeAxes = Axes.Both + } } } } }; + + selectedClip.BindValueChanged(e => selectBestVisual(e.NewValue), true); + } + + private void selectBestVisual(Clip? clip) + { + if (clip is null) + { + gridContent.FadeOut(250, Easing.OutQuad); + noClipContent.FadeIn(250, Easing.InQuad); + } + else + { + noClipContent.FadeOut(250, Easing.OutQuad); + gridContent.FadeIn(250, Easing.InQuad); + } } } From 68ee535cfff0617aa67677b2b1437e9e38c81da5 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 12:31:34 +0000 Subject: [PATCH 04/59] Expose start and end times to metadata editor --- VRCOSC.Game/ChatBox/Clip.cs | 6 +- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 6 +- .../SelectedClip/Metadata/ClipMetadataTime.cs | 58 +++++++++++++++++++ .../SelectedClipMetadataEditor.cs | 12 ++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs diff --git a/VRCOSC.Game/ChatBox/Clip.cs b/VRCOSC.Game/ChatBox/Clip.cs index f9863e97..5ba41f39 100644 --- a/VRCOSC.Game/ChatBox/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clip.cs @@ -17,8 +17,8 @@ public class Clip public readonly List AssociatedModules = new(); public readonly Dictionary States = new(); public readonly Dictionary Events = new(); + public readonly Bindable Start = new(); + public readonly Bindable End = new(); - public DateTimeOffset Start { get; private set; } - public DateTimeOffset End { get; private set; } - public TimeSpan Length => End - Start; + public TimeSpan Length => End.Value - Start.Value; } diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index ddf8e5b2..1146e9f6 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.ChatBox.SelectedClip; +using VRCOSC.Game.Graphics.ChatBox.Timeline; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox; @@ -36,7 +38,7 @@ private void load() Padding = new MarginPadding(10), Children = new Drawable[] { - new SelectedClip.SelectedClipEditorWrapper + new SelectedClipEditorWrapper { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -47,7 +49,7 @@ private void load() Bottom = 2.5f } }, - new Timeline.TimelineEditorWrapper + new TimelineEditorWrapper { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs new file mode 100644 index 00000000..73efba28 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs @@ -0,0 +1,58 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.UI.Text; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; + +public partial class ClipMetadataTime : Container +{ + public required string Label { get; init; } + public required Bindable Current { get; init; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + IntTextBox inputTextBox; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 25), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = inputTextBox = new IntTextBox + { + RelativeSizeAxes = Axes.Both, + Text = ((int)Current.Value.TotalSeconds).ToString() + } + } + }; + + inputTextBox.OnValidEntry += value => Current.Value = TimeSpan.FromSeconds(value); + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index e39a810c..12c28627 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -55,5 +55,17 @@ private void onSelectedClipChange(Clip? clip) Label = "Enabled", State = clip.Enabled }); + + metadataFlow.Add(new ClipMetadataTime + { + Label = "Start", + Current = clip.Start + }); + + metadataFlow.Add(new ClipMetadataTime + { + Label = "End", + Current = clip.End + }); } } From 0956cca72ac858aacecd7b3b94aad1d54b3efc52 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 12:36:13 +0000 Subject: [PATCH 05/59] Create ChatBoxManager --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 25 +++++++++++++++++++ VRCOSC.Game/ChatBox/{ => Clips}/Clip.cs | 2 +- VRCOSC.Game/ChatBox/{ => Clips}/ClipEvent.cs | 2 +- VRCOSC.Game/ChatBox/{ => Clips}/ClipState.cs | 2 +- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 2 +- .../SelectedClip/SelectedClipEditorWrapper.cs | 2 +- .../SelectedClipMetadataEditor.cs | 2 +- 7 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 VRCOSC.Game/ChatBox/ChatBoxManager.cs rename VRCOSC.Game/ChatBox/{ => Clips}/Clip.cs (95%) rename VRCOSC.Game/ChatBox/{ => Clips}/ClipEvent.cs (86%) rename VRCOSC.Game/ChatBox/{ => Clips}/ClipState.cs (88%) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs new file mode 100644 index 00000000..7a380807 --- /dev/null +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -0,0 +1,25 @@ +// 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 VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox; + +public class ChatBoxManager +{ + public IReadOnlyList Clips => clips; + private readonly List clips = new(); + + public Clip CreateClip() + { + var clip = new Clip(); + clips.Add(clip); + return clip; + } + + public void DeleteClip(Clip clip) + { + clips.Remove(clip); + } +} diff --git a/VRCOSC.Game/ChatBox/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs similarity index 95% rename from VRCOSC.Game/ChatBox/Clip.cs rename to VRCOSC.Game/ChatBox/Clips/Clip.cs index 5ba41f39..1d7ce994 100644 --- a/VRCOSC.Game/ChatBox/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using osu.Framework.Bindables; -namespace VRCOSC.Game.ChatBox; +namespace VRCOSC.Game.ChatBox.Clips; /// /// Represents a timespan that contains all information the ChatBox will need for displaying diff --git a/VRCOSC.Game/ChatBox/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs similarity index 86% rename from VRCOSC.Game/ChatBox/ClipEvent.cs rename to VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index 6cf87bea..090ce491 100644 --- a/VRCOSC.Game/ChatBox/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -3,7 +3,7 @@ using System; -namespace VRCOSC.Game.ChatBox; +namespace VRCOSC.Game.ChatBox.Clips; public class ClipEvent : ClipState { diff --git a/VRCOSC.Game/ChatBox/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs similarity index 88% rename from VRCOSC.Game/ChatBox/ClipState.cs rename to VRCOSC.Game/ChatBox/Clips/ClipState.cs index dfd3fbd9..8e1f1d0a 100644 --- a/VRCOSC.Game/ChatBox/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -3,7 +3,7 @@ using osu.Framework.Bindables; -namespace VRCOSC.Game.ChatBox; +namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 1146e9f6..4c73136f 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.SelectedClip; using VRCOSC.Game.Graphics.ChatBox.Timeline; using VRCOSC.Game.Graphics.Themes; diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index 8a4ee8ef..d5479de1 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 12c28627..505aa7f5 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; -using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; using VRCOSC.Game.Graphics.Themes; From 12c449a70f019205cd523928de5344507ef6fa66 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 5 Mar 2023 12:54:09 +0000 Subject: [PATCH 06/59] Create basis for timeline metadata --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 4 ++ .../MetadataTime.cs} | 28 +++++++++--- .../MetadataToggle.cs} | 4 +- .../SelectedClip/SelectedClipEditorWrapper.cs | 8 +++- .../SelectedClipMetadataEditor.cs | 8 ++-- .../ChatBox/Timeline/TimelineEditorWrapper.cs | 8 +++- .../Timeline/TimelineMetadataEditor.cs | 43 +++++++++++++++++-- VRCOSC.Game/VRCOSCGame.cs | 4 ++ 8 files changed, 87 insertions(+), 20 deletions(-) rename VRCOSC.Game/Graphics/ChatBox/{SelectedClip/Metadata/ClipMetadataTime.cs => Metadata/MetadataTime.cs} (68%) rename VRCOSC.Game/Graphics/ChatBox/{SelectedClip/Metadata/ClipMetadataToggle.cs => Metadata/MetadataToggle.cs} (93%) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 7a380807..05bc24bf 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -1,7 +1,9 @@ // 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 osu.Framework.Bindables; using VRCOSC.Game.ChatBox.Clips; namespace VRCOSC.Game.ChatBox; @@ -11,6 +13,8 @@ public class ChatBoxManager public IReadOnlyList Clips => clips; private readonly List clips = new(); + public readonly Bindable TimelineLength = new(TimeSpan.FromSeconds(60)); + public Clip CreateClip() { var clip = new Clip(); diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs similarity index 68% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs rename to VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs index 73efba28..e79e7142 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataTime.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs @@ -7,23 +7,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Text; -namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; +namespace VRCOSC.Game.Graphics.ChatBox.Metadata; -public partial class ClipMetadataTime : Container +public partial class MetadataTime : Container { public required string Label { get; init; } public required Bindable Current { get; init; } + private IntTextBox inputTextBox = null!; + [BackgroundDependencyLoader] private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - IntTextBox inputTextBox; - Children = new Drawable[] { new Container @@ -45,14 +46,29 @@ private void load() Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = inputTextBox = new IntTextBox + Child = inputTextBox = new LocalTextBox { RelativeSizeAxes = Axes.Both, - Text = ((int)Current.Value.TotalSeconds).ToString() + CornerRadius = 5 } } }; inputTextBox.OnValidEntry += value => Current.Value = TimeSpan.FromSeconds(value); } + + protected override void LoadComplete() + { + inputTextBox.Text = ((int)Current.Value.TotalSeconds).ToString(); + } + + private partial class LocalTextBox : IntTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; + } + } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs similarity index 93% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs rename to VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs index 68d3c89c..1cef941a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/Metadata/ClipMetadataToggle.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Sprites; using VRCOSC.Game.Graphics.UI.Button; -namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; +namespace VRCOSC.Game.Graphics.ChatBox.Metadata; -public partial class ClipMetadataToggle : Container +public partial class MetadataToggle : Container { public required string Label { get; init; } public required BindableBool State { get; init; } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index d5479de1..9fdf8c87 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -72,12 +72,16 @@ private void load() null, new SelectedClipModuleSelector { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 }, null, new SelectedClipStateEditor { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 505aa7f5..19de0a74 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Shapes; using osuTK; using VRCOSC.Game.ChatBox.Clips; -using VRCOSC.Game.Graphics.ChatBox.SelectedClip.Metadata; +using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -50,19 +50,19 @@ private void onSelectedClipChange(Clip? clip) if (clip is null) return; - metadataFlow.Add(new ClipMetadataToggle + metadataFlow.Add(new MetadataToggle { Label = "Enabled", State = clip.Enabled }); - metadataFlow.Add(new ClipMetadataTime + metadataFlow.Add(new MetadataTime { Label = "Start", Current = clip.Start }); - metadataFlow.Add(new ClipMetadataTime + metadataFlow.Add(new MetadataTime { Label = "End", Current = clip.End diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs index 693a5cd3..c478a82b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs @@ -27,12 +27,16 @@ private void load() { new TimelineMetadataEditor { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 }, null, new TimelineEditor { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10 } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index af316669..3c44b249 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -5,19 +5,54 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.ChatBox.Metadata; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; public partial class TimelineMetadataEditor : Container { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { - Child = new Box + Children = new Drawable[] { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = FrameworkFont.Regular.With(size: 30), + Text = "Timeline" + }, + new MetadataTime + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Label = "Length", + Current = chatBoxManager.TimelineLength + } + } + } }; } } diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index a3331b62..6eb2c68f 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Config; using VRCOSC.Game.Graphics; using VRCOSC.Game.Graphics.Notifications; @@ -49,6 +50,9 @@ public abstract partial class VRCOSCGame : VRCOSCGameBase [Cached] protected GameManager GameManager = new(); + [Cached] + protected ChatBoxManager ChatBoxManager = new(); + private NotificationContainer notificationContainer = null!; private VRCOSCUpdateManager updateManager = null!; private RouterManager routerManager = null!; From fc232deb4b86328db5f5eca900f3af58f428650c Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 6 Mar 2023 18:07:17 +0000 Subject: [PATCH 07/59] Fix older AMD CPUs not reporting temperature --- VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs b/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs index 9854418e..2eca07ce 100644 --- a/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs +++ b/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs @@ -121,6 +121,7 @@ private static void handleCPU(CPU cpu, ISensor sensor) { // AMD case @"Core (Tctl/Tdie)": + case @"Core (Tctl)": // Intel case @"CPU Package": cpu.Temperature = (int?)sensor.Value ?? 0; From 57780a65d573a045e1b40c3e3acf0d6f3cf9709b Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 9 Mar 2023 21:08:22 +0000 Subject: [PATCH 08/59] Create warning about non-initialised media hook --- VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs | 11 ++++++++--- VRCOSC.Modules/Media/MediaModule.cs | 12 ++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs index e59ae99c..4a5b6548 100644 --- a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs +++ b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Windows.Media; using Windows.Media.Control; using osu.Framework.Extensions.IEnumerableExtensions; @@ -17,21 +18,25 @@ public class WindowsMediaProvider private readonly List sessions = new(); private GlobalSystemMediaTransportControlsSessionManager? sessionManager; - public GlobalSystemMediaTransportControlsSession? Controller => sessionManager!.GetCurrentSession(); + public GlobalSystemMediaTransportControlsSession? Controller => sessionManager?.GetCurrentSession(); public Action? OnPlaybackStateUpdate; public MediaState State { get; private set; } = null!; - public async void Hook() + public async Task Hook() { State = new MediaState(); sessionManager ??= await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); - sessionManager!.CurrentSessionChanged += onCurrentSessionChanged; + if (sessionManager is null) return false; + + sessionManager.CurrentSessionChanged += onCurrentSessionChanged; sessionManager.SessionsChanged += sessionsChanged; sessionsChanged(null, null); onCurrentSessionChanged(null, null); + + return true; } public void UnHook() diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index d879bd42..aeb0479f 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -73,10 +73,18 @@ protected override void CreateAttributes() return formattedText; } - protected override void OnModuleStart() + protected override async void OnModuleStart() { base.OnModuleStart(); - mediaProvider.Hook(); + + var result = await mediaProvider.Hook(); + + if (!result) + { + Log("Could not hook into Windows media"); + Log("Try restarting the modules\nIf this persists you will need to restart your PC as Windows has not initialised media correctly"); + } + startProcesses(); } From 5f7b93b6d1578368a4fcf13f63db89a92e610358 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:33:51 +0000 Subject: [PATCH 09/59] Bump LibreHardwareMonitorLib from 0.9.1 to 0.9.2 Bumps [LibreHardwareMonitorLib](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor) from 0.9.1 to 0.9.2. - [Release notes](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/releases) - [Commits](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/compare/v0.9.1...v0.9.2) --- updated-dependencies: - dependency-name: LibreHardwareMonitorLib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index a69cf84d..5bfc51f0 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -17,7 +17,7 @@ - + From d5aaebb2bad3fc31c99b3dba6b1f8bfafc7946ac Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 26 Mar 2023 16:34:49 +0100 Subject: [PATCH 10/59] Convert `ModuleManager` to allow source injection --- VRCOSC.Game/Modules/GameManager.cs | 19 ++++- VRCOSC.Game/Modules/IModuleManager.cs | 16 ++++ VRCOSC.Game/Modules/ModuleManager.cs | 81 +++++-------------- .../Modules/Sources/DLLModuleSource.cs | 19 +++++ .../Modules/Sources/ExternalModuleSource.cs | 31 +++++++ VRCOSC.Game/Modules/Sources/IModuleSource.cs | 12 +++ .../Modules/Sources/InternalModuleSource.cs | 26 ++++++ 7 files changed, 143 insertions(+), 61 deletions(-) create mode 100644 VRCOSC.Game/Modules/IModuleManager.cs create mode 100644 VRCOSC.Game/Modules/Sources/DLLModuleSource.cs create mode 100644 VRCOSC.Game/Modules/Sources/ExternalModuleSource.cs create mode 100644 VRCOSC.Game/Modules/Sources/IModuleSource.cs create mode 100644 VRCOSC.Game/Modules/Sources/InternalModuleSource.cs diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 95716edc..0a7b67d5 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -16,6 +16,7 @@ using VRCOSC.Game.Config; using VRCOSC.Game.Graphics.Notifications; using VRCOSC.Game.Modules.Avatar; +using VRCOSC.Game.Modules.Sources; using VRCOSC.Game.OpenVR; using VRCOSC.Game.OpenVR.Metadata; using VRCOSC.Game.OSC; @@ -45,6 +46,9 @@ public partial class GameManager : CompositeComponent [Resolved(name: "InfoModule")] private Bindable infoModule { get; set; } = null!; + [Resolved] + private Storage storage { get; set; } = null!; + private Bindable autoStartStop = null!; private bool hasAutoStarted; private readonly List oscDataCache = new(); @@ -60,7 +64,7 @@ public partial class GameManager : CompositeComponent public AvatarConfig? AvatarConfig; [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { autoStartStop = configManager.GetBindable(VRCOSCSetting.AutoStartStop); @@ -77,6 +81,14 @@ private void load(Storage storage) ChatBoxInterface = new ChatBoxInterface(VRChatOscClient, configManager.GetBindable(VRCOSCSetting.ChatBoxTimeSpan)); + setupModules(); + } + + private void setupModules() + { + ModuleManager.AddSource(new InternalModuleSource()); + ModuleManager.AddSource(new ExternalModuleSource(storage)); + ModuleManager.Load(); AddInternal(ModuleManager); } @@ -145,7 +157,10 @@ private void handleOscDataCache() } } - ModuleManager.OnParameterReceived(data); + foreach (var module in ModuleManager) + { + module.OnParameterReceived(data); + } }); oscDataCache.Clear(); } diff --git a/VRCOSC.Game/Modules/IModuleManager.cs b/VRCOSC.Game/Modules/IModuleManager.cs new file mode 100644 index 00000000..b8d91eb9 --- /dev/null +++ b/VRCOSC.Game/Modules/IModuleManager.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. + +using System.Collections.Generic; +using VRCOSC.Game.Modules.Sources; + +namespace VRCOSC.Game.Modules; + +public interface IModuleManager : IEnumerable +{ + public void AddSource(IModuleSource source); + public bool RemoveSource(IModuleSource source); + public void Load(); + public void Start(); + public void Stop(); +} diff --git a/VRCOSC.Game/Modules/ModuleManager.cs b/VRCOSC.Game/Modules/ModuleManager.cs index f6c14f1c..f89168b8 100644 --- a/VRCOSC.Game/Modules/ModuleManager.cs +++ b/VRCOSC.Game/Modules/ModuleManager.cs @@ -4,74 +4,45 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; -using osu.Framework.Logging; -using osu.Framework.Platform; -using VRCOSC.Game.OSC.VRChat; +using VRCOSC.Game.Modules.Sources; namespace VRCOSC.Game.Modules; -public sealed partial class ModuleManager : CompositeComponent, IEnumerable +public sealed partial class ModuleManager : CompositeComponent, IModuleManager { - private readonly TerminalLogger terminal = new(nameof(ModuleManager)); + private static TerminalLogger terminal => new("ModuleManager"); - private readonly SortedList tempModuleList = new(); + private readonly List sources = new(); + private readonly SortedList modules = new(); - [Resolved] - private Storage storage { get; set; } = null!; + public void AddSource(IModuleSource source) => sources.Add(source); + public bool RemoveSource(IModuleSource source) => sources.Remove(source); - [BackgroundDependencyLoader] - private void load() + public void Load() { - loadInternalModules(); - loadExternalModules(); - AddRangeInternal(tempModuleList); - } - - private void loadInternalModules() - { - var assemblyPath = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories) - .FirstOrDefault(fileName => fileName.Contains("VRCOSC.Modules")); + modules.Clear(); - if (string.IsNullOrEmpty(assemblyPath)) + sources.ForEach(source => { - Logger.Log("Could not find internal module assembly"); - return; - } - - loadModuleAssembly(assemblyPath); - } - - private void loadExternalModules() - { - var moduleDirectoryPath = storage.GetStorageForDirectory("custom").GetFullPath(string.Empty, true); - Directory.GetFiles(moduleDirectoryPath, "*.dll", SearchOption.AllDirectories) - .ForEach(loadModuleAssembly); - } - - private void loadModuleAssembly(string assemblyPath) - { - Assembly.LoadFile(assemblyPath).GetTypes() - .Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract) - .Select(type => (Module)Activator.CreateInstance(type)!) - .ForEach(module => tempModuleList.Add(module)); + foreach (var type in source.Load()) + { + var module = (Module)Activator.CreateInstance(type)!; + modules.Add(module); + } + }); + + AddRangeInternal(modules); } public void Start() { - if (this.All(module => !module.Enabled.Value)) - { + if (modules.All(module => !module.Enabled.Value)) terminal.Log("You have no modules selected!\nSelect some modules to begin using VRCOSC"); - return; - } - foreach (var module in this) + foreach (var module in modules) { module.Start(); } @@ -79,20 +50,12 @@ public void Start() public void Stop() { - foreach (var module in this) + foreach (var module in modules) { module.Stop(); } } - public void OnParameterReceived(VRChatOscData data) - { - foreach (var module in this) - { - module.OnParameterReceived(data); - } - } - - public IEnumerator GetEnumerator() => InternalChildren.Select(child => (Module)child).GetEnumerator(); + public IEnumerator GetEnumerator() => modules.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/VRCOSC.Game/Modules/Sources/DLLModuleSource.cs b/VRCOSC.Game/Modules/Sources/DLLModuleSource.cs new file mode 100644 index 00000000..1a7a75b8 --- /dev/null +++ b/VRCOSC.Game/Modules/Sources/DLLModuleSource.cs @@ -0,0 +1,19 @@ +// 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.Reflection; + +namespace VRCOSC.Game.Modules.Sources; + +public abstract class DLLModuleSource : IModuleSource +{ + protected IEnumerable LoadModulesFromDLL(string dllPath) + { + return Assembly.LoadFile(dllPath).GetTypes().Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract); + } + + public abstract IEnumerable Load(); +} diff --git a/VRCOSC.Game/Modules/Sources/ExternalModuleSource.cs b/VRCOSC.Game/Modules/Sources/ExternalModuleSource.cs new file mode 100644 index 00000000..4b5cf831 --- /dev/null +++ b/VRCOSC.Game/Modules/Sources/ExternalModuleSource.cs @@ -0,0 +1,31 @@ +// 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.IO; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Platform; + +namespace VRCOSC.Game.Modules.Sources; + +public class ExternalModuleSource : DLLModuleSource +{ + private const string custom_directory = "custom"; + + private readonly Storage storage; + + public ExternalModuleSource(Storage storage) + { + this.storage = storage; + } + + public override IEnumerable Load() + { + var moduleDirectoryPath = storage.GetStorageForDirectory(custom_directory).GetFullPath(string.Empty, true); + + var moduleList = new List(); + Directory.GetFiles(moduleDirectoryPath, "*.dll", SearchOption.AllDirectories).ForEach(dllPath => moduleList.AddRange(LoadModulesFromDLL(dllPath))); + return moduleList; + } +} diff --git a/VRCOSC.Game/Modules/Sources/IModuleSource.cs b/VRCOSC.Game/Modules/Sources/IModuleSource.cs new file mode 100644 index 00000000..e48c8618 --- /dev/null +++ b/VRCOSC.Game/Modules/Sources/IModuleSource.cs @@ -0,0 +1,12 @@ +// 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; + +namespace VRCOSC.Game.Modules.Sources; + +public interface IModuleSource +{ + public IEnumerable Load(); +} diff --git a/VRCOSC.Game/Modules/Sources/InternalModuleSource.cs b/VRCOSC.Game/Modules/Sources/InternalModuleSource.cs new file mode 100644 index 00000000..954b0686 --- /dev/null +++ b/VRCOSC.Game/Modules/Sources/InternalModuleSource.cs @@ -0,0 +1,26 @@ +// 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.IO; +using System.Linq; +using osu.Framework.Logging; + +namespace VRCOSC.Game.Modules.Sources; + +public class InternalModuleSource : DLLModuleSource +{ + public override IEnumerable Load() + { + var dllPath = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories).FirstOrDefault(fileName => fileName.Contains("VRCOSC.Modules")); + + if (string.IsNullOrEmpty(dllPath)) + { + Logger.Log("Could not find internal module assembly"); + return new List(); + } + + return LoadModulesFromDLL(dllPath); + } +} From 0443b455b77a538c4d794bad79944f2a1176c1d5 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 26 Mar 2023 17:20:25 +0100 Subject: [PATCH 11/59] Extract serialisation into IModuleSerialiser --- VRCOSC.Game/Modules/GameManager.cs | 6 +- .../Modules/{ => Manager}/IModuleManager.cs | 4 +- .../Modules/{ => Manager}/ModuleManager.cs | 19 ++++- VRCOSC.Game/Modules/Module.cs | 14 +-- .../Serialisation/IModuleSerialiser.cs | 10 +++ .../ModuleSerialiser.cs} | 85 +++++++++---------- 6 files changed, 77 insertions(+), 61 deletions(-) rename VRCOSC.Game/Modules/{ => Manager}/IModuleManager.cs (75%) rename VRCOSC.Game/Modules/{ => Manager}/ModuleManager.cs (74%) create mode 100644 VRCOSC.Game/Modules/Serialisation/IModuleSerialiser.cs rename VRCOSC.Game/Modules/{Module_IO.cs => Serialisation/ModuleSerialiser.cs} (78%) diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 0a7b67d5..218f733c 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -16,6 +16,8 @@ using VRCOSC.Game.Config; using VRCOSC.Game.Graphics.Notifications; using VRCOSC.Game.Modules.Avatar; +using VRCOSC.Game.Modules.Manager; +using VRCOSC.Game.Modules.Serialisation; using VRCOSC.Game.Modules.Sources; using VRCOSC.Game.OpenVR; using VRCOSC.Game.OpenVR.Metadata; @@ -55,8 +57,8 @@ public partial class GameManager : CompositeComponent private readonly object oscDataCacheLock = new(); public readonly VRChatOscClient VRChatOscClient = new(); - public readonly ModuleManager ModuleManager = new(); public readonly Bindable State = new(GameManagerState.Stopped); + public ModuleManager ModuleManager = null!; public OSCRouter OSCRouter = null!; public Player Player = null!; public OVRClient OVRClient = null!; @@ -86,8 +88,10 @@ private void load() private void setupModules() { + ModuleManager = new ModuleManager(); ModuleManager.AddSource(new InternalModuleSource()); ModuleManager.AddSource(new ExternalModuleSource(storage)); + ModuleManager.SetSerialiser(new ModuleSerialiser(storage)); ModuleManager.Load(); AddInternal(ModuleManager); } diff --git a/VRCOSC.Game/Modules/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs similarity index 75% rename from VRCOSC.Game/Modules/IModuleManager.cs rename to VRCOSC.Game/Modules/Manager/IModuleManager.cs index b8d91eb9..78843533 100644 --- a/VRCOSC.Game/Modules/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -2,14 +2,16 @@ // See the LICENSE file in the repository root for full license text. using System.Collections.Generic; +using VRCOSC.Game.Modules.Serialisation; using VRCOSC.Game.Modules.Sources; -namespace VRCOSC.Game.Modules; +namespace VRCOSC.Game.Modules.Manager; public interface IModuleManager : IEnumerable { public void AddSource(IModuleSource source); public bool RemoveSource(IModuleSource source); + public void SetSerialiser(IModuleSerialiser serialiser); public void Load(); public void Start(); public void Stop(); diff --git a/VRCOSC.Game/Modules/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs similarity index 74% rename from VRCOSC.Game/Modules/ModuleManager.cs rename to VRCOSC.Game/Modules/Manager/ModuleManager.cs index f89168b8..a44bce09 100644 --- a/VRCOSC.Game/Modules/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -7,9 +7,11 @@ using System.Linq; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; +using osu.Framework.Logging; +using VRCOSC.Game.Modules.Serialisation; using VRCOSC.Game.Modules.Sources; -namespace VRCOSC.Game.Modules; +namespace VRCOSC.Game.Modules.Manager; public sealed partial class ModuleManager : CompositeComponent, IModuleManager { @@ -17,9 +19,11 @@ public sealed partial class ModuleManager : CompositeComponent, IModuleManager private readonly List sources = new(); private readonly SortedList modules = new(); + private IModuleSerialiser? serialiser; public void AddSource(IModuleSource source) => sources.Add(source); public bool RemoveSource(IModuleSource source) => sources.Remove(source); + public void SetSerialiser(IModuleSerialiser serialiser) => this.serialiser = serialiser; public void Load() { @@ -34,7 +38,20 @@ public void Load() } }); + // TODO - Remove after decoupling AddRangeInternal(modules); + + if (serialiser is null) + { + Logger.Log("Warning. No serialiser has been set. Aborting module load"); + return; + } + + foreach (var module in this) + { + module.Load(); + serialiser?.Deserialise(module); + } } public void Start() diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index 6e702396..f6ca0e3c 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -30,7 +30,6 @@ public abstract partial class Module : Component, IComparable [Resolved] private IVRCOSCSecrets secrets { get; set; } = null!; - private Storage Storage = null!; private TerminalLogger Terminal = null!; protected Player Player => GameManager.Player; @@ -56,7 +55,7 @@ public abstract partial class Module : Component, IComparable private bool IsEnabled => Enabled.Value; private bool ShouldUpdate => DeltaUpdate != TimeSpan.MaxValue; - private string FileName => @$"{GetType().Name}.ini"; + internal string FileName => @$"{GetType().Name}.ini"; protected bool IsStarting => State.Value == ModuleState.Starting; protected bool HasStarted => State.Value == ModuleState.Started; @@ -66,21 +65,13 @@ public abstract partial class Module : Component, IComparable internal bool HasSettings => Settings.Any(); internal bool HasParameters => Parameters.Any(); - [BackgroundDependencyLoader] - internal void load(Storage storage) + public void Load() { - Storage = storage.GetStorageForDirectory("modules"); Terminal = new TerminalLogger(Title); CreateAttributes(); Parameters.ForEach(pair => ParametersLookup.Add(pair.Key.ToLookup(), pair.Key)); - - performLoad(); - } - - protected override void LoadComplete() - { State.ValueChanged += _ => Log(State.Value.ToString()); } @@ -139,6 +130,7 @@ internal void Start() OnModuleStart(); + // TODO - Get scheduler from ModuleManager if (ShouldUpdate) Scheduler.AddDelayed(OnModuleUpdate, DeltaUpdate.TotalMilliseconds, true); State.Value = ModuleState.Started; diff --git a/VRCOSC.Game/Modules/Serialisation/IModuleSerialiser.cs b/VRCOSC.Game/Modules/Serialisation/IModuleSerialiser.cs new file mode 100644 index 00000000..7706ac47 --- /dev/null +++ b/VRCOSC.Game/Modules/Serialisation/IModuleSerialiser.cs @@ -0,0 +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.Serialisation; + +public interface IModuleSerialiser +{ + public void Deserialise(Module module); + public void Serialise(Module module); +} diff --git a/VRCOSC.Game/Modules/Module_IO.cs b/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs similarity index 78% rename from VRCOSC.Game/Modules/Module_IO.cs rename to VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs index a53081aa..33008665 100644 --- a/VRCOSC.Game/Modules/Module_IO.cs +++ b/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs @@ -7,16 +7,23 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; +using osu.Framework.Platform; -namespace VRCOSC.Game.Modules; +namespace VRCOSC.Game.Modules.Serialisation; -public partial class Module +public class ModuleSerialiser : IModuleSerialiser { - #region Loading + private const string directory_name = "modules"; + private readonly Storage storage; - private void performLoad() + public ModuleSerialiser(Storage storage) { - using (var stream = Storage.GetStream(FileName)) + this.storage = storage.GetStorageForDirectory(directory_name); + } + + public void Deserialise(Module module) + { + using (var stream = storage.GetStream(module.FileName)) { if (stream is not null) { @@ -27,25 +34,26 @@ private void performLoad() switch (line) { case "#InternalSettings": - performInternalSettingsLoad(reader); + performInternalSettingsLoad(reader, module); break; case "#Settings": - performSettingsLoad(reader); + performSettingsLoad(reader, module); break; case "#Parameters": - performParametersLoad(reader); + performParametersLoad(reader, module); break; } } } } - executeAfterLoad(); + Serialise(module); + module.Enabled.BindValueChanged(_ => Serialise(module)); } - private void performInternalSettingsLoad(TextReader reader) + private static void performInternalSettingsLoad(TextReader reader, Module module) { while (reader.ReadLine() is { } line) { @@ -58,13 +66,13 @@ private void performInternalSettingsLoad(TextReader reader) switch (lookup) { case "enabled": - Enabled.Value = bool.Parse(value); + module.Enabled.Value = bool.Parse(value); break; } } } - private void performSettingsLoad(TextReader reader) + private static void performSettingsLoad(TextReader reader, Module module) { while (reader.ReadLine() is { } line) { @@ -80,9 +88,9 @@ private void performSettingsLoad(TextReader reader) var lookup = lookupStr; if (lookupStr.Contains('#')) lookup = lookupStr.Split(new[] { '#' }, 2)[0]; - if (!Settings.ContainsKey(lookup)) continue; + if (!module.Settings.ContainsKey(lookup)) continue; - var setting = Settings[lookup]; + var setting = module.Settings[lookup]; switch (setting) { @@ -157,7 +165,7 @@ private void performSettingsLoad(TextReader reader) } } - private void performParametersLoad(TextReader reader) + private static void performParametersLoad(TextReader reader, Module module) { while (reader.ReadLine() is { } line) { @@ -167,19 +175,13 @@ private void performParametersLoad(TextReader reader) var lookup = lineSplit[0]; var value = lineSplit[1]; - if (!ParametersLookup.ContainsKey(lookup)) continue; + if (!module.ParametersLookup.ContainsKey(lookup)) continue; - var parameter = Parameters[ParametersLookup[lookup]]; + var parameter = module.Parameters[module.ParametersLookup[lookup]]; parameter.Attribute.Value = value; } } - private void executeAfterLoad() - { - performSave(); - Enabled.BindValueChanged(_ => performSave()); - } - private static Type? enumNameToType(string enumName) { Type? returnType = null; @@ -201,40 +203,31 @@ private void executeAfterLoad() return returnType; } - #endregion - - #region Saving - - public void Save() - { - performSave(); - } - - private void performSave() + public void Serialise(Module module) { - using var stream = Storage.CreateFileSafely(FileName); + using var stream = storage.CreateFileSafely(module.FileName); using var writer = new StreamWriter(stream); - performInternalSettingsSave(writer); - performSettingsSave(writer); - performParametersSave(writer); + performInternalSettingsSave(writer, module); + performSettingsSave(writer, module); + performParametersSave(writer, module); } - private void performInternalSettingsSave(TextWriter writer) + private static void performInternalSettingsSave(TextWriter writer, Module module) { writer.WriteLine(@"#InternalSettings"); - writer.WriteLine(@"{0}={1}", "enabled", Enabled.Value.ToString()); + writer.WriteLine(@"{0}={1}", "enabled", module.Enabled.Value.ToString()); writer.WriteLine(@"#End"); } - private void performSettingsSave(TextWriter writer) + private static void performSettingsSave(TextWriter writer, Module module) { - var areAllDefault = Settings.All(pair => pair.Value.IsDefault()); + var areAllDefault = module.Settings.All(pair => pair.Value.IsDefault()); if (areAllDefault) return; writer.WriteLine(@"#Settings"); - foreach (var (lookup, moduleAttributeData) in Settings) + foreach (var (lookup, moduleAttributeData) in module.Settings) { if (moduleAttributeData.IsDefault()) continue; @@ -286,14 +279,14 @@ private void performSettingsSave(TextWriter writer) writer.WriteLine(@"#End"); } - private void performParametersSave(TextWriter writer) + private static void performParametersSave(TextWriter writer, Module module) { - var areAllDefault = Parameters.All(pair => pair.Value.IsDefault()); + var areAllDefault = module.Parameters.All(pair => pair.Value.IsDefault()); if (areAllDefault) return; writer.WriteLine(@"#Parameters"); - foreach (var (lookup, parameterAttribute) in Parameters) + foreach (var (lookup, parameterAttribute) in module.Parameters) { if (parameterAttribute.IsDefault()) continue; @@ -303,6 +296,4 @@ private void performParametersSave(TextWriter writer) writer.WriteLine(@"#End"); } - - #endregion } From 6e6326aaf6f83766fcb3e8765c17cad1416cdb01 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 26 Mar 2023 17:44:35 +0100 Subject: [PATCH 12/59] Decouple `Module` from `Component` and provide custom scheduler --- VRCOSC.Game/Modules/GameManager.cs | 19 +++++++---- VRCOSC.Game/Modules/Manager/IModuleManager.cs | 1 + VRCOSC.Game/Modules/Manager/ModuleManager.cs | 34 ++++++++++++++++--- VRCOSC.Game/Modules/Module.cs | 33 ++++++++++-------- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 218f733c..f85f3468 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -9,9 +9,11 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; +using osu.Framework.Development; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using Valve.VR; using VRCOSC.Game.Config; using VRCOSC.Game.Graphics.Notifications; @@ -27,7 +29,7 @@ namespace VRCOSC.Game.Modules; -public partial class GameManager : CompositeComponent +public partial class GameManager : Component { private const double openvr_check_interval = 1000; private const double vrchat_process_check_interval = 5000; @@ -51,6 +53,12 @@ public partial class GameManager : CompositeComponent [Resolved] private Storage storage { get; set; } = null!; + [Resolved] + private GameHost host { get; set; } = null!; + + [Resolved] + private IVRCOSCSecrets secrets { get; set; } = null!; + private Bindable autoStartStop = null!; private bool hasAutoStarted; private readonly List oscDataCache = new(); @@ -92,8 +100,8 @@ private void setupModules() ModuleManager.AddSource(new InternalModuleSource()); ModuleManager.AddSource(new ExternalModuleSource(storage)); ModuleManager.SetSerialiser(new ModuleSerialiser(storage)); + ModuleManager.InjectModuleDependencies(host, this, secrets, new Scheduler(() => ThreadSafety.IsUpdateThread, Clock)); ModuleManager.Load(); - AddInternal(ModuleManager); } protected override void Update() @@ -103,11 +111,8 @@ protected override void Update() OVRClient.Update(); handleOscDataCache(); - } - protected override void UpdateAfterChildren() - { - if (State.Value != GameManagerState.Started) return; + ModuleManager.Update(); ChatBoxInterface.Update(); } diff --git a/VRCOSC.Game/Modules/Manager/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs index 78843533..1a7eab98 100644 --- a/VRCOSC.Game/Modules/Manager/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -14,5 +14,6 @@ public interface IModuleManager : IEnumerable public void SetSerialiser(IModuleSerialiser serialiser); public void Load(); public void Start(); + public void Update(); public void Stop(); } diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index a44bce09..903f39b5 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -5,15 +5,16 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Threading; using VRCOSC.Game.Modules.Serialisation; using VRCOSC.Game.Modules.Sources; namespace VRCOSC.Game.Modules.Manager; -public sealed partial class ModuleManager : CompositeComponent, IModuleManager +public sealed class ModuleManager : IModuleManager { private static TerminalLogger terminal => new("ModuleManager"); @@ -25,6 +26,19 @@ public sealed partial class ModuleManager : CompositeComponent, IModuleManager public bool RemoveSource(IModuleSource source) => sources.Remove(source); public void SetSerialiser(IModuleSerialiser serialiser) => this.serialiser = serialiser; + private GameHost host = null!; + private GameManager gameManager = null!; + private IVRCOSCSecrets secrets = null!; + private Scheduler scheduler = null!; + + public void InjectModuleDependencies(GameHost host, GameManager gameManager, IVRCOSCSecrets secrets, Scheduler scheduler) + { + this.host = host; + this.gameManager = gameManager; + this.secrets = secrets; + this.scheduler = scheduler; + } + public void Load() { modules.Clear(); @@ -34,13 +48,11 @@ public void Load() foreach (var type in source.Load()) { var module = (Module)Activator.CreateInstance(type)!; + module.InjectDependencies(host, gameManager, secrets, scheduler); modules.Add(module); } }); - // TODO - Remove after decoupling - AddRangeInternal(modules); - if (serialiser is null) { Logger.Log("Warning. No serialiser has been set. Aborting module load"); @@ -65,8 +77,20 @@ public void Start() } } + public void Update() + { + scheduler.Update(); + + foreach (var module in modules) + { + module.internalUpdate(); + } + } + public void Stop() { + scheduler.CancelDelayedTasks(); + foreach (var module in modules) { module.Stop(); diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index f6ca0e3c..15f770e2 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -4,12 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Framework.Threading; using VRCOSC.Game.Modules.Avatar; using VRCOSC.Game.OpenVR; using VRCOSC.Game.OSC.VRChat; @@ -19,16 +18,12 @@ namespace VRCOSC.Game.Modules; -public abstract partial class Module : Component, IComparable +public abstract class Module : IComparable { - [Resolved] - private GameHost Host { get; set; } = null!; - - [Resolved] - private GameManager GameManager { get; set; } = null!; - - [Resolved] - private IVRCOSCSecrets secrets { get; set; } = null!; + private GameHost Host = null!; + private GameManager GameManager = null!; + protected IVRCOSCSecrets Secrets { get; private set; } = null!; + private Scheduler Scheduler = null!; private TerminalLogger Terminal = null!; @@ -37,7 +32,6 @@ public abstract partial class Module : Component, IComparable protected VRChatOscClient OscClient => GameManager.VRChatOscClient; protected ChatBoxInterface ChatBoxInterface => GameManager.ChatBoxInterface; protected Bindable State = new(ModuleState.Stopped); - protected IVRCOSCSecrets Secrets => secrets; protected AvatarConfig? AvatarConfig => GameManager.AvatarConfig; internal readonly BindableBool Enabled = new(); @@ -65,6 +59,14 @@ public abstract partial class Module : Component, IComparable internal bool HasSettings => Settings.Any(); internal bool HasParameters => Parameters.Any(); + public void InjectDependencies(GameHost host, GameManager gameManager, IVRCOSCSecrets secrets, Scheduler scheduler) + { + Host = host; + GameManager = gameManager; + Secrets = secrets; + Scheduler = scheduler; + } + public void Load() { Terminal = new TerminalLogger(Title); @@ -130,7 +132,6 @@ internal void Start() OnModuleStart(); - // TODO - Get scheduler from ModuleManager if (ShouldUpdate) Scheduler.AddDelayed(OnModuleUpdate, DeltaUpdate.TotalMilliseconds, true); State.Value = ModuleState.Started; @@ -138,14 +139,16 @@ internal void Start() if (ShouldUpdateImmediately) OnModuleUpdate(); } + // TODO - Proxy for now until ChatBoxV3 is decoupled from ChatBoxModule + internal void internalUpdate() => Update(); + protected virtual void Update() { } + internal void Stop() { if (!IsEnabled) return; State.Value = ModuleState.Stopping; - Scheduler.CancelDelayedTasks(); - OnModuleStop(); State.Value = ModuleState.Stopped; From 260842c31a6ab1e68878f265fb7ff23f2e41c404 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 26 Mar 2023 17:51:27 +0100 Subject: [PATCH 13/59] Re-implement module saving from GUI --- .../ModuleEditing/ModuleEditingPopover.cs | 7 ++++++- VRCOSC.Game/Modules/GameManager.cs | 15 ++++++++------- VRCOSC.Game/Modules/Manager/IModuleManager.cs | 2 ++ VRCOSC.Game/Modules/Manager/ModuleManager.cs | 18 ++++++++++++------ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs index 37b89f19..5cc1b134 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs @@ -15,6 +15,9 @@ public sealed partial class ModuleEditingPopover : PopoverScreen [Resolved(name: "EditingModule")] private Bindable editingModule { get; set; } = null!; + [Resolved] + private GameManager gameManager { get; set; } = null!; + public ModuleEditingPopover() { Children = new Drawable[] @@ -43,7 +46,9 @@ protected override void LoadComplete() { if (e.NewValue is null) { - e.OldValue?.Save(); + if (e.OldValue is not null) + gameManager.ModuleManager.Save(e.OldValue); + Hide(); } else diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index f85f3468..62d8421d 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -66,7 +66,7 @@ public partial class GameManager : Component public readonly VRChatOscClient VRChatOscClient = new(); public readonly Bindable State = new(GameManagerState.Stopped); - public ModuleManager ModuleManager = null!; + public IModuleManager ModuleManager = null!; public OSCRouter OSCRouter = null!; public Player Player = null!; public OVRClient OVRClient = null!; @@ -96,12 +96,13 @@ private void load() private void setupModules() { - ModuleManager = new ModuleManager(); - ModuleManager.AddSource(new InternalModuleSource()); - ModuleManager.AddSource(new ExternalModuleSource(storage)); - ModuleManager.SetSerialiser(new ModuleSerialiser(storage)); - ModuleManager.InjectModuleDependencies(host, this, secrets, new Scheduler(() => ThreadSafety.IsUpdateThread, Clock)); - ModuleManager.Load(); + var moduleManager = new ModuleManager(); + moduleManager.AddSource(new InternalModuleSource()); + moduleManager.AddSource(new ExternalModuleSource(storage)); + moduleManager.SetSerialiser(new ModuleSerialiser(storage)); + moduleManager.InjectModuleDependencies(host, this, secrets, new Scheduler(() => ThreadSafety.IsUpdateThread, Clock)); + moduleManager.Load(); + ModuleManager = moduleManager; } protected override void Update() diff --git a/VRCOSC.Game/Modules/Manager/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs index 1a7eab98..dc976221 100644 --- a/VRCOSC.Game/Modules/Manager/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -13,6 +13,8 @@ public interface IModuleManager : IEnumerable public bool RemoveSource(IModuleSource source); public void SetSerialiser(IModuleSerialiser serialiser); public void Load(); + public void SaveAll(); + public void Save(Module module); public void Start(); public void Update(); public void Stop(); diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index 903f39b5..07430227 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Lists; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using VRCOSC.Game.Modules.Serialisation; @@ -53,19 +52,26 @@ public void Load() } }); - if (serialiser is null) + foreach (var module in this) { - Logger.Log("Warning. No serialiser has been set. Aborting module load"); - return; + module.Load(); + serialiser?.Deserialise(module); } + } + public void SaveAll() + { foreach (var module in this) { - module.Load(); - serialiser?.Deserialise(module); + serialiser?.Serialise(module); } } + public void Save(Module module) + { + serialiser?.Serialise(module); + } + public void Start() { if (modules.All(module => !module.Enabled.Value)) From ec4bc09b9d23d0528a8459d847316b1891e8146a Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 26 Mar 2023 23:29:27 +0100 Subject: [PATCH 14/59] Small changes --- VRCOSC.Game/Modules/GameManager.cs | 13 ++++++------- VRCOSC.Game/Modules/Manager/IModuleManager.cs | 3 +++ VRCOSC.Game/Modules/Manager/ModuleManager.cs | 1 + .../Modules/Serialisation/ModuleSerialiser.cs | 1 - 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 62d8421d..a9ef75a1 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -96,13 +96,12 @@ private void load() private void setupModules() { - var moduleManager = new ModuleManager(); - moduleManager.AddSource(new InternalModuleSource()); - moduleManager.AddSource(new ExternalModuleSource(storage)); - moduleManager.SetSerialiser(new ModuleSerialiser(storage)); - moduleManager.InjectModuleDependencies(host, this, secrets, new Scheduler(() => ThreadSafety.IsUpdateThread, Clock)); - moduleManager.Load(); - ModuleManager = moduleManager; + ModuleManager = new ModuleManager(); + ModuleManager.AddSource(new InternalModuleSource()); + ModuleManager.AddSource(new ExternalModuleSource(storage)); + ModuleManager.SetSerialiser(new ModuleSerialiser(storage)); + ModuleManager.InjectModuleDependencies(host, this, secrets, new Scheduler(() => ThreadSafety.IsUpdateThread, Clock)); + ModuleManager.Load(); } protected override void Update() diff --git a/VRCOSC.Game/Modules/Manager/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs index dc976221..3576dd7a 100644 --- a/VRCOSC.Game/Modules/Manager/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -2,6 +2,8 @@ // See the LICENSE file in the repository root for full license text. using System.Collections.Generic; +using osu.Framework.Platform; +using osu.Framework.Threading; using VRCOSC.Game.Modules.Serialisation; using VRCOSC.Game.Modules.Sources; @@ -12,6 +14,7 @@ public interface IModuleManager : IEnumerable public void AddSource(IModuleSource source); public bool RemoveSource(IModuleSource source); public void SetSerialiser(IModuleSerialiser serialiser); + public void InjectModuleDependencies(GameHost host, GameManager gameManager, IVRCOSCSecrets secrets, Scheduler scheduler); public void Load(); public void SaveAll(); public void Save(Module module); diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index 07430227..c217151c 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -56,6 +56,7 @@ public void Load() { module.Load(); serialiser?.Deserialise(module); + module.Enabled.BindValueChanged(_ => serialiser?.Serialise(module)); } } diff --git a/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs b/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs index 33008665..c0ae3b60 100644 --- a/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs +++ b/VRCOSC.Game/Modules/Serialisation/ModuleSerialiser.cs @@ -50,7 +50,6 @@ public void Deserialise(Module module) } Serialise(module); - module.Enabled.BindValueChanged(_ => Serialise(module)); } private static void performInternalSettingsLoad(TextReader reader, Module module) From 86781dc34f7510d8d6b226fa2d38de362a3587e8 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 2 Apr 2023 16:35:04 +0100 Subject: [PATCH 15/59] Create AFKModule --- VRCOSC.Modules/AFK/AFKModule.cs | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 VRCOSC.Modules/AFK/AFKModule.cs diff --git a/VRCOSC.Modules/AFK/AFKModule.cs b/VRCOSC.Modules/AFK/AFKModule.cs new file mode 100644 index 00000000..52a32a30 --- /dev/null +++ b/VRCOSC.Modules/AFK/AFKModule.cs @@ -0,0 +1,52 @@ +// 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.Modules; + +namespace VRCOSC.Modules.AFK; + +public class AFKModule : ChatBoxModule +{ + public override string Title => "AFK Display"; + public override string Description => "Display text and time since going AFK"; + public override string Author => "VolcanicArts"; + public override ModuleType Type => ModuleType.General; + protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(1.5f); + protected override int ChatBoxPriority => 4; + protected override IEnumerable ChatBoxFormatValues => new[] { "%duration%" }; + protected override string DefaultChatBoxFormat => "AFK for %duration%"; + + private bool isAFK; + private DateTime? afkBegan; + + protected override void OnModuleStart() + { + isAFK = false; + afkBegan = null; + } + + protected override string? GetChatBoxText() + { + if (afkBegan is null) return null; + + return GetSetting(ChatBoxSetting.ChatBoxFormat) + .Replace("%duration%", (DateTime.Now - afkBegan.Value).ToString(@"hh\:mm\:ss")); + } + + protected override void OnModuleUpdate() + { + if (Player.AFK is null) return; + + if (Player.AFK.Value && !isAFK) + { + afkBegan = DateTime.Now; + isAFK = true; + } + + if (!Player.AFK.Value && isAFK) + { + afkBegan = null; + isAFK = false; + } + } +} From 0f981d431ee8fd71ab753024624ab52816af6d56 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 2 Apr 2023 18:13:42 +0100 Subject: [PATCH 16/59] Create DrawableClip and implement timeline editing --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 13 +- VRCOSC.Game/ChatBox/ChatBoxVariable.cs | 14 ++ VRCOSC.Game/ChatBox/Clips/Clip.cs | 25 +++- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 3 +- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 2 - .../Graphics/ChatBox/Metadata/MetadataTime.cs | 16 +- .../SelectedClipMetadataEditor.cs | 4 + .../Graphics/ChatBox/Timeline/DrawableClip.cs | 140 ++++++++++++++++++ .../ChatBox/Timeline/TimelineEditor.cs | 37 ++++- .../Timeline/TimelineMetadataEditor.cs | 17 ++- 10 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 VRCOSC.Game/ChatBox/ChatBoxVariable.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 05bc24bf..ffab19df 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -13,16 +13,19 @@ public class ChatBoxManager public IReadOnlyList Clips => clips; private readonly List clips = new(); - public readonly Bindable TimelineLength = new(TimeSpan.FromSeconds(60)); + public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); - public Clip CreateClip() + public ChatBoxManager() + { + AddClip(new Clip()); + } + + public void AddClip(Clip clip) { - var clip = new Clip(); clips.Add(clip); - return clip; } - public void DeleteClip(Clip clip) + public void RemoveClip(Clip clip) { clips.Remove(clip); } diff --git a/VRCOSC.Game/ChatBox/ChatBoxVariable.cs b/VRCOSC.Game/ChatBox/ChatBoxVariable.cs new file mode 100644 index 00000000..84494794 --- /dev/null +++ b/VRCOSC.Game/ChatBox/ChatBoxVariable.cs @@ -0,0 +1,14 @@ +// 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.ChatBox; + +/// +/// Used by modules to denote a provided variable +/// +public class ChatBoxVariable +{ + public required string Lookup { get; init; } + public required string Format { get; init; } + public required string Description { get; init; } +} diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 1d7ce994..6d7a14be 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -1,7 +1,6 @@ // 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 osu.Framework.Bindables; @@ -14,11 +13,27 @@ public class Clip { public readonly BindableBool Enabled = new(true); public readonly Bindable Name = new("New Clip"); - public readonly List AssociatedModules = new(); + public readonly BindableList AssociatedModules = new(); + public readonly BindableList AvailableVariables = new(); public readonly Dictionary States = new(); public readonly Dictionary Events = new(); - public readonly Bindable Start = new(); - public readonly Bindable End = new(); + public readonly Bindable Start = new(); + public readonly Bindable End = new(0.5f); - public TimeSpan Length => End.Value - Start.Value; + public float Length => End.Value - Start.Value; + + public Clip() + { + AssociatedModules.BindCollectionChanged((_, _) => calculateAvailableVariables(), true); + } + + private void calculateAvailableVariables() + { + AvailableVariables.Clear(); + + foreach (var module in AssociatedModules) + { + //AvailableVariables.AddRange(module.Variables); + } + } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index 8e1f1d0a..c3b1a94c 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -7,6 +7,7 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { - public string Format = string.Empty; + public required string Name { get; init; } + public string Format { get; init; } = string.Empty; public BindableBool Enabled = new(); } diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 4c73136f..080e8e4c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -21,8 +21,6 @@ public partial class ChatBoxScreen : Container [BackgroundDependencyLoader] private void load() { - selectedClip.Value = new Clip(); - RelativeSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs index e79e7142..394c47da 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs @@ -1,12 +1,13 @@ // 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.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Text; @@ -14,8 +15,11 @@ namespace VRCOSC.Game.Graphics.ChatBox.Metadata; public partial class MetadataTime : Container { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + public required string Label { get; init; } - public required Bindable Current { get; init; } + public required Bindable Current { get; init; } private IntTextBox inputTextBox = null!; @@ -49,17 +53,17 @@ private void load() Child = inputTextBox = new LocalTextBox { RelativeSizeAxes = Axes.Both, - CornerRadius = 5 + CornerRadius = 5, + // TODO - Split this out + ReadOnly = true } } }; - - inputTextBox.OnValidEntry += value => Current.Value = TimeSpan.FromSeconds(value); } protected override void LoadComplete() { - inputTextBox.Text = ((int)Current.Value.TotalSeconds).ToString(); + Current.BindValueChanged(_ => inputTextBox.Text = (Current.Value * (float)chatBoxManager.TimelineLength.Value.TotalSeconds).ToString(CultureInfo.InvariantCulture), true); } private partial class LocalTextBox : IntTextBox diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 19de0a74..1a425c97 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; @@ -15,6 +16,9 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipMetadataEditor : Container { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] private Bindable selectedClip { get; set; } = null!; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs new file mode 100644 index 00000000..4bf9ea53 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -0,0 +1,140 @@ +// 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.Allocation; +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.Input.Events; +using osuTK.Graphics; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; + +public partial class DrawableClip : Container +{ + [Resolved] + private Bindable selectedClip { get; set; } = null!; + + private readonly Clip clip; + + public DrawableClip(Clip clip) + { + this.clip = clip; + } + + [BackgroundDependencyLoader] + private void load() + { + Height = 120; + RelativeSizeAxes = Axes.X; + RelativePositionAxes = Axes.X; + Masking = true; + CornerRadius = 5; + BorderColour = Colour4.Aqua.Darken(0.5f); + + clip.Start.BindValueChanged(_ => updateSizeAndPosition()); + clip.End.BindValueChanged(_ => updateSizeAndPosition()); + updateSizeAndPosition(); + + selectedClip.BindValueChanged(e => + { + BorderThickness = clip == e.NewValue ? 3 : 0; + }, true); + + Children = new Drawable[] + { + new Box + { + Colour = Color4.OrangeRed, + RelativeSizeAxes = Axes.Both, + }, + new ResizeDetector(clip.Start, v => v / Parent.DrawWidth) + { + RelativeSizeAxes = Axes.Y, + Width = 20 + }, + new ResizeDetector(clip.End, v => v / Parent.DrawWidth) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 20 + } + }; + } + + protected override bool OnClick(ClickEvent e) + { + selectedClip.Value = clip; + return true; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + selectedClip.Value = clip; + + e.Target = Parent; + var delta = e.Delta.X / Parent.DrawWidth; + // TODO - Apply snapping + // TODO - Apply limits + clip.Start.Value += delta; + clip.End.Value += delta; + } + + private void updateSizeAndPosition() + { + Width = clip.Length; + X = clip.Start.Value; + } + + private partial class ResizeDetector : Container + { + private readonly Bindable value; + private readonly Func normaliseFunc; + + public ResizeDetector(Bindable value, Func normaliseFunc) + { + this.value = value; + this.normaliseFunc = normaliseFunc; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new Box + { + Colour = Color4.Black.Opacity(0f), + RelativeSizeAxes = Axes.Both + }; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + e.Target = Parent.Parent; + value.Value += normaliseFunc.Invoke(e.Delta.X); + } + + protected override bool OnHover(HoverEvent e) + { + Child.FadeColour(Colour4.Black.Opacity(0.5f), 100); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Child.FadeColour(Colour4.Black.Opacity(0f), 100); + } + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index c890466c..75f0197c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -2,22 +2,51 @@ // See the LICENSE file in the repository root for full license text. 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 osuTK.Graphics; +using osu.Framework.Input.Events; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; +[Cached] public partial class TimelineEditor : Container { + [Resolved] + private Bindable selectedClip { get; set; } = null!; + + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + private Container clipContainer = null!; + [BackgroundDependencyLoader] private void load() { - Child = new Box + Children = new Drawable[] { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both, + }, + clipContainer = new Container + { + RelativeSizeAxes = Axes.Both + } }; + + chatBoxManager.Clips.ForEach(clip => clipContainer.Add(new DrawableClip(clip))); + } + + protected override bool OnClick(ClickEvent e) + { + selectedClip.Value = null; + return true; } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index 3c44b249..fca21c10 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; -using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -21,6 +20,8 @@ public partial class TimelineMetadataEditor : Container [BackgroundDependencyLoader] private void load() { + // TODO - Add snap resolution + Children = new Drawable[] { new Box @@ -44,13 +45,13 @@ private void load() Font = FrameworkFont.Regular.With(size: 30), Text = "Timeline" }, - new MetadataTime - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Label = "Length", - Current = chatBoxManager.TimelineLength - } + // new MetadataTime + // { + // Anchor = Anchor.TopCentre, + // Origin = Anchor.TopCentre, + // Label = "Length", + // Current = chatBoxManager.TimelineLength + // } } } }; From 1bcf5fc8d59b1ca819cda03a8715a68917ef5884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:00:33 +0000 Subject: [PATCH 17/59] Bump PolySharp from 1.12.1 to 1.13.1 Bumps [PolySharp](https://github.com/Sergio0694/PolySharp) from 1.12.1 to 1.13.1. - [Release notes](https://github.com/Sergio0694/PolySharp/releases) - [Commits](https://github.com/Sergio0694/PolySharp/compare/1.12.1...1.13.1) --- updated-dependencies: - dependency-name: PolySharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index a69cf84d..9ac2df57 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -20,7 +20,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From d53ad6879eb9a200be9aaa7e466052b521ceadb7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Fri, 7 Apr 2023 13:00:29 +0100 Subject: [PATCH 18/59] Add basic timeline snapping --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 2 + VRCOSC.Game/ChatBox/Clips/Clip.cs | 2 +- .../Graphics/ChatBox/Metadata/MetadataTime.cs | 10 +- .../ChatBox/Metadata/ReadonlyTimeDisplay.cs | 84 +++++++++++ .../SelectedClipMetadataEditor.cs | 4 +- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 131 ++++++++++++++---- .../ChatBox/Timeline/TimelineEditor.cs | 61 +++++++- .../ChatBox/Timeline/TimelineLayer.cs | 36 +++++ .../Timeline/TimelineMetadataEditor.cs | 15 +- .../RectangularPositionSnappingGrid.cs | 130 +++++++++++++++++ 10 files changed, 430 insertions(+), 45 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs create mode 100644 VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index ffab19df..7e4c8fff 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -15,6 +15,8 @@ public class ChatBoxManager public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); + public float Resolution => 1f / (float)TimelineLength.Value.TotalSeconds; + public ChatBoxManager() { AddClip(new Clip()); diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 6d7a14be..154b90f9 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -13,13 +13,13 @@ public class Clip { public readonly BindableBool Enabled = new(true); public readonly Bindable Name = new("New Clip"); + public readonly Bindable Priority = new(); public readonly BindableList AssociatedModules = new(); public readonly BindableList AvailableVariables = new(); public readonly Dictionary States = new(); public readonly Dictionary Events = new(); public readonly Bindable Start = new(); public readonly Bindable End = new(0.5f); - public float Length => End.Value - Start.Value; public Clip() diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs index 394c47da..53f66106 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.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 System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,7 +20,7 @@ public partial class MetadataTime : Container private ChatBoxManager chatBoxManager { get; set; } = null!; public required string Label { get; init; } - public required Bindable Current { get; init; } + public required Bindable Current { get; init; } private IntTextBox inputTextBox = null!; @@ -53,9 +54,7 @@ private void load() Child = inputTextBox = new LocalTextBox { RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - // TODO - Split this out - ReadOnly = true + CornerRadius = 5 } } }; @@ -63,7 +62,8 @@ private void load() protected override void LoadComplete() { - Current.BindValueChanged(_ => inputTextBox.Text = (Current.Value * (float)chatBoxManager.TimelineLength.Value.TotalSeconds).ToString(CultureInfo.InvariantCulture), true); + inputTextBox.Text = Current.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture); + inputTextBox.OnValidEntry += value => Current.Value = TimeSpan.FromSeconds(value); } private partial class LocalTextBox : IntTextBox diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs new file mode 100644 index 00000000..eed87b86 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs @@ -0,0 +1,84 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Globalization; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; + +namespace VRCOSC.Game.Graphics.ChatBox.Metadata; + +public partial class ReadonlyTimeDisplay : Container +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + public required string Label { get; init; } + public required Bindable Current { get; init; } + + private VRCOSCTextBox textBox = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 25), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = textBox = new LocalTextBox + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + ReadOnly = true + } + } + }; + } + + protected override void LoadComplete() + { + chatBoxManager.TimelineLength.BindValueChanged(_ => updateText()); + Current.BindValueChanged(_ => updateText()); + updateText(); + } + + private void updateText() + { + textBox.Text = (chatBoxManager.TimelineLength.Value.TotalSeconds * Current.Value).ToString("##0", CultureInfo.InvariantCulture); + } + + private partial class LocalTextBox : VRCOSCTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; + } + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 1a425c97..6a4b1e2c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -60,13 +60,13 @@ private void onSelectedClipChange(Clip? clip) State = clip.Enabled }); - metadataFlow.Add(new MetadataTime + metadataFlow.Add(new ReadonlyTimeDisplay { Label = "Start", Current = clip.Start }); - metadataFlow.Add(new MetadataTime + metadataFlow.Add(new ReadonlyTimeDisplay { Label = "End", Current = clip.End diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index 4bf9ea53..85f17f51 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -9,8 +9,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osuTK; using osuTK.Graphics; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -19,8 +22,16 @@ public partial class DrawableClip : Container [Resolved] private Bindable selectedClip { get; set; } = null!; + [Resolved] + private TimelineEditor timelineEditor { get; set; } = null!; + + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + private readonly Clip clip; + private float cumulativeDrag; + public DrawableClip(Clip clip) { this.clip = clip; @@ -29,35 +40,35 @@ public DrawableClip(Clip clip) [BackgroundDependencyLoader] private void load() { - Height = 120; - RelativeSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.X; Masking = true; - CornerRadius = 5; - BorderColour = Colour4.Aqua.Darken(0.5f); + CornerRadius = 10; + BorderColour = ThemeManager.Current[ThemeAttribute.Lighter]; + // TODO - Solve unbind issue after 2nd+ selection clip.Start.BindValueChanged(_ => updateSizeAndPosition()); clip.End.BindValueChanged(_ => updateSizeAndPosition()); updateSizeAndPosition(); selectedClip.BindValueChanged(e => { - BorderThickness = clip == e.NewValue ? 3 : 0; + BorderThickness = clip == e.NewValue ? 4 : 2; }, true); Children = new Drawable[] { new Box { - Colour = Color4.OrangeRed, - RelativeSizeAxes = Axes.Both, + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both }, - new ResizeDetector(clip.Start, v => v / Parent.DrawWidth) + new StartResizeDetector(clip, v => v / Parent.DrawWidth) { RelativeSizeAxes = Axes.Y, Width = 20 }, - new ResizeDetector(clip.End, v => v / Parent.DrawWidth) + new EndResizeDetector(clip, v => v / Parent.DrawWidth) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -82,11 +93,25 @@ protected override void OnDrag(DragEvent e) selectedClip.Value = clip; e.Target = Parent; - var delta = e.Delta.X / Parent.DrawWidth; - // TODO - Apply snapping - // TODO - Apply limits - clip.Start.Value += delta; - clip.End.Value += delta; + + var deltaX = e.Delta.X / Parent.DrawWidth; + cumulativeDrag += deltaX; + + if (Math.Abs(cumulativeDrag) >= chatBoxManager.Resolution) + { + var newStart = clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newEnd = clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); + + if (newStart >= 0 && newEnd <= 1) + { + clip.Start.Value = newStart; + clip.End.Value = newEnd; + } + + cumulativeDrag = 0f; + } + + // TODO - Implement delta Y for dragging between priorities } private void updateSizeAndPosition() @@ -97,13 +122,15 @@ private void updateSizeAndPosition() private partial class ResizeDetector : Container { - private readonly Bindable value; - private readonly Func normaliseFunc; + protected readonly Clip Clip; + protected readonly Func NormaliseFunc; + + protected float CumulativeDrag; - public ResizeDetector(Bindable value, Func normaliseFunc) + public ResizeDetector(Clip clip, Func normaliseFunc) { - this.value = value; - this.normaliseFunc = normaliseFunc; + Clip = clip; + NormaliseFunc = normaliseFunc; } [BackgroundDependencyLoader] @@ -118,23 +145,77 @@ private void load() protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnHover(HoverEvent e) + { + Child.FadeColour(Colour4.Black.Opacity(0.5f), 100); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Child.FadeColour(Colour4.Black.Opacity(0f), 100); + } + } + + private partial class StartResizeDetector : ResizeDetector + { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + public StartResizeDetector(Clip clip, Func normaliseFunc) + : base(clip, normaliseFunc) + { + } + protected override void OnDrag(DragEvent e) { base.OnDrag(e); e.Target = Parent.Parent; - value.Value += normaliseFunc.Invoke(e.Delta.X); + CumulativeDrag += NormaliseFunc.Invoke(e.Delta.X); + + if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) + { + var newStart = Clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); + + if (newStart < Clip.End.Value && newStart >= 0) + { + Clip.Start.Value = newStart; + } + + CumulativeDrag = 0f; + } } + } - protected override bool OnHover(HoverEvent e) + private partial class EndResizeDetector : ResizeDetector + { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + public EndResizeDetector(Clip clip, Func normaliseFunc) + : base(clip, normaliseFunc) { - Child.FadeColour(Colour4.Black.Opacity(0.5f), 100); - return true; } - protected override void OnHoverLost(HoverLostEvent e) + protected override void OnDrag(DragEvent e) { - Child.FadeColour(Colour4.Black.Opacity(0f), 100); + base.OnDrag(e); + + e.Target = Parent.Parent; + CumulativeDrag += NormaliseFunc.Invoke(e.Delta.X); + + if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) + { + var newEnd = Clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); + + if (newEnd > Clip.Start.Value && newEnd <= 1) + { + Clip.End.Value = newEnd; + } + + CumulativeDrag = 0f; + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 75f0197c..45e4f310 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osuTK; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; @@ -23,25 +24,73 @@ public partial class TimelineEditor : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - private Container clipContainer = null!; + private RectangularPositionSnapGrid snapping = null!; [BackgroundDependencyLoader] private void load() { + TimelineLayer layer3; + Children = new Drawable[] { new Box { - Colour = ThemeManager.Current[ThemeAttribute.Dark], - RelativeSizeAxes = Axes.Both, + Colour = ThemeManager.Current[ThemeAttribute.Mid], + RelativeSizeAxes = Axes.Both }, - clipContainer = new Container + snapping = new RectangularPositionSnapGrid(Vector2.Zero) { RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(2), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + layer3 = new TimelineLayer() + }, + null, + new Drawable[] + { + new TimelineLayer() + }, + null, + new Drawable[] + { + new TimelineLayer() + }, + null, + new Drawable[] + { + new TimelineLayer() + } + } + } } }; - chatBoxManager.Clips.ForEach(clip => clipContainer.Add(new DrawableClip(clip))); + chatBoxManager.Clips.ForEach(clip => layer3.Add(clip)); + } + + protected override void LoadComplete() + { + chatBoxManager.TimelineLength.BindValueChanged(e => snapping.Spacing = new Vector2(DrawWidth / (float)e.NewValue.TotalSeconds, 175), true); } protected override bool OnClick(ClickEvent e) @@ -49,4 +98,6 @@ protected override bool OnClick(ClickEvent e) selectedClip.Value = null; return true; } + + public Vector2 GetSnappedPosition(Vector2 pos) => snapping.GetSnappedPosition(pos); } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs new file mode 100644 index 00000000..26cdae28 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -0,0 +1,36 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; + +public partial class TimelineLayer : Container +{ + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + // new Box + // { + // Colour = ThemeManager.Current[ThemeAttribute.Dark], + // RelativeSizeAxes = Axes.Both, + // } + }; + } + + public void Add(Clip clip) + { + Add(new DrawableClip(clip)); + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index fca21c10..25b4d1d1 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -45,13 +46,13 @@ private void load() Font = FrameworkFont.Regular.With(size: 30), Text = "Timeline" }, - // new MetadataTime - // { - // Anchor = Anchor.TopCentre, - // Origin = Anchor.TopCentre, - // Label = "Length", - // Current = chatBoxManager.TimelineLength - // } + new MetadataTime + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Label = "Length", + Current = chatBoxManager.TimelineLength + } } } }; diff --git a/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs b/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs new file mode 100644 index 00000000..08213166 --- /dev/null +++ b/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs @@ -0,0 +1,130 @@ +// 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osu.Framework.Utils; +using osuTK; + +namespace VRCOSC.Game.Graphics; + +// Taken from https://github.com/ppy/osu/blob/1122ee967c346b2b3454ac0a19a928e723717aff/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +public partial class RectangularPositionSnapGrid : CompositeDrawable +{ + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition { get; } + + private Vector2 spacing = Vector2.One; + + /// + /// The spacing between grid lines of this . + /// + public Vector2 Spacing + { + get => spacing; + set + { + if (spacing.X <= 0 || spacing.Y <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + gridCache.Invalidate(); + } + } + + private readonly LayoutValue gridCache = new(Invalidation.RequiredParentSizeToFit); + + public RectangularPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + + AddLayout(gridCache); + } + + protected override void Update() + { + base.Update(); + + if (!gridCache.IsValid) + { + ClearInternal(); + createContent(); + gridCache.Validate(); + } + } + + private void createContent() + { + var drawSize = DrawSize; + + generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); + generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); + + generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); + generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); + } + + private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) + { + int index = 0; + float currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width * 7f; + + var generatedLines = new List(); + + while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0)) + { + var gridLine = new Box + { + Colour = Colour4.Black, + Alpha = 0.1f + }; + + if (direction == Direction.Horizontal) + { + gridLine.Origin = Anchor.CentreLeft; + gridLine.RelativeSizeAxes = Axes.X; + gridLine.Height = lineWidth; + gridLine.Y = currentPosition; + } + else + { + gridLine.Origin = Anchor.TopCentre; + gridLine.RelativeSizeAxes = Axes.Y; + gridLine.Width = lineWidth; + gridLine.X = currentPosition; + } + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 1f; + generatedLines.Last().Alpha = 1f; + + AddRangeInternal(generatedLines); + } + + public Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = original - StartPosition; + Vector2 offset = Vector2.Divide(relativeToStart, Spacing); + Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); + + return StartPosition + Vector2.Multiply(roundedOffset, Spacing); + } +} From cc1462df0bec0c5a07fe8b3ee4b4b35e5816bd29 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 11:09:15 +0100 Subject: [PATCH 19/59] Add basis for right click menu --- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 8 +- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 1 - .../ChatBox/Timeline/Menu/TimelineMenu.cs | 79 +++++++++++++++++++ .../ChatBox/Timeline/TimelineEditor.cs | 14 +++- .../ChatBox/Timeline/TimelineLayer.cs | 18 ++++- .../Timeline/TimelineMetadataEditor.cs | 15 ++-- 6 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 080e8e4c..11bde12a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -9,15 +9,20 @@ using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.SelectedClip; using VRCOSC.Game.Graphics.ChatBox.Timeline; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox; +[Cached] public partial class ChatBoxScreen : Container { [Cached] private Bindable selectedClip { get; set; } = new(); + [Cached] + private TimelineMenu timelineMenu = new(); + [BackgroundDependencyLoader] private void load() { @@ -59,7 +64,8 @@ private void load() } } } - } + }, + timelineMenu }; } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index 85f17f51..db1c3e4a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osuTK; using osuTK.Graphics; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs new file mode 100644 index 00000000..c6381f34 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs @@ -0,0 +1,79 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Platform; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; + +public partial class TimelineMenu : VisibilityContainer +{ + [Resolved] + private GameHost host { get; set; } = null!; + + [Resolved] + private ChatBoxScreen chatBoxScreen { get; set; } = null!; + + private Container innerContainer = null!; + + [BackgroundDependencyLoader] + private void load() + { + Child = innerContainer = new Container + { + Width = 200, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 300 + } + } + } + } + }; + } + + protected override void PopIn() + { + Alpha = 1; + + if (Position.Y + innerContainer.DrawHeight < host.Window.ClientSize.Height) + innerContainer.Origin = innerContainer.Anchor = Position.X + innerContainer.DrawWidth < host.Window.ClientSize.Width ? Anchor.TopLeft : Anchor.TopRight; + else + innerContainer.Origin = innerContainer.Anchor = Position.X + innerContainer.DrawWidth < host.Window.ClientSize.Width ? Anchor.BottomLeft : Anchor.BottomRight; + } + + protected override void PopOut() + { + Alpha = 0; + } + + public void SetPosition(MouseDownEvent e) + { + e.Target = chatBoxScreen; + Position = e.MousePosition; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 45e4f310..f4139768 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -11,6 +11,7 @@ using osuTK; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -24,6 +25,9 @@ public partial class TimelineEditor : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] + private TimelineMenu timelineMenu { get; set; } = null!; + private RectangularPositionSnapGrid snapping = null!; [BackgroundDependencyLoader] @@ -88,6 +92,13 @@ private void load() chatBoxManager.Clips.ForEach(clip => layer3.Add(clip)); } + public void ShowMenu(MouseDownEvent e) + { + timelineMenu.Hide(); + timelineMenu.SetPosition(e); + timelineMenu.Show(); + } + protected override void LoadComplete() { chatBoxManager.TimelineLength.BindValueChanged(e => snapping.Spacing = new Vector2(DrawWidth / (float)e.NewValue.TotalSeconds, 175), true); @@ -96,8 +107,7 @@ protected override void LoadComplete() protected override bool OnClick(ClickEvent e) { selectedClip.Value = null; + timelineMenu.Hide(); return true; } - - public Vector2 GetSnappedPosition(Vector2 pos) => snapping.GetSnappedPosition(pos); } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index 26cdae28..ecd4c2d4 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -4,14 +4,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osuTK.Input; using VRCOSC.Game.ChatBox.Clips; -using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; public partial class TimelineLayer : Container { + [Resolved] + private TimelineEditor timelineEditor { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -33,4 +36,15 @@ public void Add(Clip clip) { Add(new DrawableClip(clip)); } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Right) + { + timelineEditor.ShowMenu(e); + return true; + } + + return base.OnMouseDown(e); + } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index 25b4d1d1..fca21c10 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; -using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -46,13 +45,13 @@ private void load() Font = FrameworkFont.Regular.With(size: 30), Text = "Timeline" }, - new MetadataTime - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Label = "Length", - Current = chatBoxManager.TimelineLength - } + // new MetadataTime + // { + // Anchor = Anchor.TopCentre, + // Origin = Anchor.TopCentre, + // Label = "Length", + // Current = chatBoxManager.TimelineLength + // } } } }; From 637e05b19068b2c01c6fc18328608a47c9c48338 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 11:22:05 +0100 Subject: [PATCH 20/59] Optimise run screen hierarchy --- .../Graphics/ModuleRun/ModuleRunPopover.cs | 18 +++++- .../Graphics/ModuleRun/ParameterContainer.cs | 58 +++++++------------ .../Graphics/ModuleRun/TerminalContainer.cs | 56 +++++++----------- 3 files changed, 57 insertions(+), 75 deletions(-) diff --git a/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs b/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs index ec301e11..0f77f0ce 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs @@ -37,14 +37,26 @@ public ModuleRunPopover() ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.35f), + new Dimension(GridSizeMode.Absolute, 15), new Dimension() }, Content = new[] { - new Drawable[] + new Drawable?[] { - terminal = new TerminalContainer(), - parameters = new ParameterContainer() + terminal = new TerminalContainer + { + RelativeSizeAxes = Axes.Both, + BorderThickness = 3, + Masking = true, + }, + null, + parameters = new ParameterContainer + { + RelativeSizeAxes = Axes.Both, + BorderThickness = 3, + Masking = true, + } } } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs index 80f7d850..8d471112 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs @@ -21,18 +21,13 @@ public sealed partial class ParameterContainer : Container public ParameterContainer() { - RelativeSizeAxes = Axes.Both; - Padding = new MarginPadding - { - Left = 15 / 2f - }; - Child = new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] { new Dimension(), + new Dimension(GridSizeMode.Absolute, 15), new Dimension() }, Content = new[] @@ -41,22 +36,21 @@ public ParameterContainer() { outgoingParameterDisplay = new ParameterSubContainer { - Title = "Outgoing", - Padding = new MarginPadding - { - Bottom = 15 / 2f - } + RelativeSizeAxes = Axes.Both, + BorderThickness = 3, + Masking = true, + Title = "Outgoing" } }, + null, new Drawable[] { incomingParameterDisplay = new ParameterSubContainer { - Title = "Incoming", - Padding = new MarginPadding - { - Top = 15 / 2f - } + RelativeSizeAxes = Axes.Both, + BorderThickness = 3, + Masking = true, + Title = "Incoming" } } } @@ -94,30 +88,22 @@ private sealed partial class ParameterSubContainer : Container [BackgroundDependencyLoader] private void load() { - RelativeSizeAxes = Axes.Both; - - Child = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - BorderThickness = 3, - Masking = true, - Children = new Drawable[] + new Box { - new Box + RelativeSizeAxes = Axes.Both, + Colour = ThemeManager.Current[ThemeAttribute.Darker] + }, + parameterDisplay = new ParameterDisplay + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Colour = ThemeManager.Current[ThemeAttribute.Darker] + Vertical = 1.5f, + Horizontal = 3 }, - parameterDisplay = new ParameterDisplay - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 1.5f, - Horizontal = 3 - }, - Title = Title - } + Title = Title } }; } diff --git a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs index d3339c5d..6322bb32 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs @@ -21,48 +21,32 @@ public sealed partial class TerminalContainer : Container public TerminalContainer() { - RelativeSizeAxes = Axes.Both; - Padding = new MarginPadding + InternalChildren = new Drawable[] { - Right = 15 / 2f - }; - - InternalChild = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new Container + new Box { RelativeSizeAxes = Axes.Both, - BorderThickness = 3, - Masking = true, - Children = new Drawable[] + Colour = ThemeManager.Current[ThemeAttribute.Darker] + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(3f), + Child = terminalScroll = new BasicScrollContainer { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ThemeManager.Current[ThemeAttribute.Darker] - }, - new Container + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + ClampExtension = 0, + Child = Content = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(3f), - Child = terminalScroll = new BasicScrollContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - ClampExtension = 0, - Child = Content = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding - { - Horizontal = 3 - } - } + Horizontal = 3 } } } From 3fd3aeb5203ef761a71ccbb769c0045137c4ab0c Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 12:44:51 +0100 Subject: [PATCH 21/59] Create layer and clip menus and start clip intersecting --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 28 ++-- VRCOSC.Game/ChatBox/Clips/Clip.cs | 11 +- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 11 +- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 80 +++++++--- .../Timeline/Menu/Clip/TimelineClipMenu.cs | 58 +++++++ .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 38 +++++ .../ChatBox/Timeline/Menu/MenuButton.cs | 16 ++ .../ChatBox/Timeline/Menu/TimelineMenu.cs | 44 +++--- .../ChatBox/Timeline/TimelineEditor.cs | 149 ++++++++++++++++-- .../ChatBox/Timeline/TimelineLayer.cs | 22 +-- .../RectangularPositionSnappingGrid.cs | 2 +- VRCOSC.Game/Graphics/UI/Button/TextButton.cs | 2 +- 12 files changed, 372 insertions(+), 89 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/MenuButton.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 7e4c8fff..f2fdcfc0 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using VRCOSC.Game.ChatBox.Clips; @@ -10,8 +11,7 @@ namespace VRCOSC.Game.ChatBox; public class ChatBoxManager { - public IReadOnlyList Clips => clips; - private readonly List clips = new(); + public readonly BindableList Clips = new(); public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); @@ -19,16 +19,26 @@ public class ChatBoxManager public ChatBoxManager() { - AddClip(new Clip()); + for (var i = 0; i < 6; i++) + { + Clips.Add(new Clip + { + Priority = { Value = i } + }); + } } - public void AddClip(Clip clip) - { - clips.Add(clip); - } + public bool IncreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value + 1); + public bool DecreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value - 1); - public void RemoveClip(Clip clip) + public bool SetPriority(Clip clip, int priority) { - clips.Remove(clip); + if (priority is > 5 or < 0) return false; + if (RetrieveClipsWithPriority(priority).Any(clip.Intersects)) return false; + + clip.Priority.Value = priority; + return true; } + + public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); } diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 154b90f9..2780aaf9 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -13,7 +13,7 @@ public class Clip { public readonly BindableBool Enabled = new(true); public readonly Bindable Name = new("New Clip"); - public readonly Bindable Priority = new(); + public readonly BindableNumber Priority = new(); public readonly BindableList AssociatedModules = new(); public readonly BindableList AvailableVariables = new(); public readonly Dictionary States = new(); @@ -36,4 +36,13 @@ private void calculateAvailableVariables() //AvailableVariables.AddRange(module.Variables); } } + + public bool Intersects(Clip other) + { + if (other.Start.Value >= Start.Value && other.Start.Value <= End.Value) return true; + if (other.End.Value >= Start.Value && other.End.Value <= End.Value) return true; + if (other.Start.Value <= Start.Value && other.End.Value >= End.Value) return true; + + return false; + } } diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 11bde12a..3b327dfa 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -9,7 +9,8 @@ using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.SelectedClip; using VRCOSC.Game.Graphics.ChatBox.Timeline; -using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Clip; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Layer; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox; @@ -21,7 +22,10 @@ public partial class ChatBoxScreen : Container private Bindable selectedClip { get; set; } = new(); [Cached] - private TimelineMenu timelineMenu = new(); + private TimelineLayerMenu layerMenu = new(); + + [Cached] + private TimelineClipMenu clipMenu = new(); [BackgroundDependencyLoader] private void load() @@ -65,7 +69,8 @@ private void load() } } }, - timelineMenu + layerMenu, + clipMenu }; } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index db1c3e4a..b81d1f66 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -8,14 +8,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK.Graphics; +using osuTK.Input; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; +[Cached] public partial class DrawableClip : Container { [Resolved] @@ -27,13 +31,13 @@ public partial class DrawableClip : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - private readonly Clip clip; + public readonly Clip Clip; private float cumulativeDrag; public DrawableClip(Clip clip) { - this.clip = clip; + Clip = clip; } [BackgroundDependencyLoader] @@ -45,16 +49,15 @@ private void load() CornerRadius = 10; BorderColour = ThemeManager.Current[ThemeAttribute.Lighter]; - // TODO - Solve unbind issue after 2nd+ selection - clip.Start.BindValueChanged(_ => updateSizeAndPosition()); - clip.End.BindValueChanged(_ => updateSizeAndPosition()); updateSizeAndPosition(); selectedClip.BindValueChanged(e => { - BorderThickness = clip == e.NewValue ? 4 : 2; + BorderThickness = Clip == e.NewValue ? 4 : 2; }, true); + SpriteText drawName; + Children = new Drawable[] { new Box @@ -62,24 +65,49 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Light], RelativeSizeAxes = Axes.Both }, - new StartResizeDetector(clip, v => v / Parent.DrawWidth) + new StartResizeDetector(Clip, v => v / Parent.DrawWidth) { RelativeSizeAxes = Axes.Y, Width = 20 }, - new EndResizeDetector(clip, v => v / Parent.DrawWidth) + new EndResizeDetector(Clip, v => v / Parent.DrawWidth) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = 20 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + drawName = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = FrameworkFont.Regular.With(size: 20) + } + } } }; + + Clip.Name.BindValueChanged(e => drawName.Text = e.NewValue, true); } - protected override bool OnClick(ClickEvent e) + protected override bool OnMouseDown(MouseDownEvent e) { - selectedClip.Value = clip; + if (e.Button == MouseButton.Left) + { + timelineEditor.HideClipMenu(); + selectedClip.Value = Clip; + } + else if (e.Button == MouseButton.Right) + { + timelineEditor.ShowClipMenu(Clip, e); + } + return true; } @@ -89,7 +117,7 @@ protected override void OnDrag(DragEvent e) { base.OnDrag(e); - selectedClip.Value = clip; + selectedClip.Value = Clip; e.Target = Parent; @@ -98,25 +126,25 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(cumulativeDrag) >= chatBoxManager.Resolution) { - var newStart = clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); - var newEnd = clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newStart = Clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newEnd = Clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); - if (newStart >= 0 && newEnd <= 1) + if (Precision.AlmostBigger(newStart, 0) && Precision.AlmostBigger(-1 * newEnd, -1)) { - clip.Start.Value = newStart; - clip.End.Value = newEnd; + Clip.Start.Value = newStart; + Clip.End.Value = newEnd; } cumulativeDrag = 0f; } - // TODO - Implement delta Y for dragging between priorities + updateSizeAndPosition(); } private void updateSizeAndPosition() { - Width = clip.Length; - X = clip.Start.Value; + Width = Clip.Length; + X = Clip.Start.Value; } private partial class ResizeDetector : Container @@ -161,6 +189,9 @@ private partial class StartResizeDetector : ResizeDetector [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] + private DrawableClip parentDrawableClip { get; set; } = null!; + public StartResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { @@ -177,13 +208,15 @@ protected override void OnDrag(DragEvent e) { var newStart = Clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); - if (newStart < Clip.End.Value && newStart >= 0) + if (Precision.AlmostBigger(newStart, 0) && Precision.AlmostBigger(-1 * newStart, -1 * Clip.End.Value)) { Clip.Start.Value = newStart; } CumulativeDrag = 0f; } + + parentDrawableClip.updateSizeAndPosition(); } } @@ -192,6 +225,9 @@ private partial class EndResizeDetector : ResizeDetector [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] + private DrawableClip parentDrawableClip { get; set; } = null!; + public EndResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { @@ -208,13 +244,15 @@ protected override void OnDrag(DragEvent e) { var newEnd = Clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); - if (newEnd > Clip.Start.Value && newEnd <= 1) + if (Precision.AlmostBigger(newEnd, Clip.Start.Value) && Precision.AlmostBigger(-1 * newEnd, -1)) { Clip.End.Value = newEnd; } CumulativeDrag = 0f; } + + parentDrawableClip.updateSizeAndPosition(); } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs new file mode 100644 index 00000000..40f6b52f --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs @@ -0,0 +1,58 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using VRCOSC.Game.ChatBox; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Clip; + +public partial class TimelineClipMenu : TimelineMenu +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + private Game.ChatBox.Clips.Clip clip; + + [BackgroundDependencyLoader] + private void load() + { + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 25, + Child = new MenuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Move Up", + FontSize = 20, + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Action = () => chatBoxManager.IncreasePriority(clip) + } + }); + + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 25, + Child = new MenuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Move Down", + FontSize = 20, + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Action = () => chatBoxManager.DecreasePriority(clip) + } + }); + } + + public void SetClip(Game.ChatBox.Clips.Clip clip) + { + this.clip = clip; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs new file mode 100644 index 00000000..2a230128 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -0,0 +1,38 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Layer; + +public partial class TimelineLayerMenu : TimelineMenu +{ + [BackgroundDependencyLoader] + private void load() + { + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 25, + Child = new MenuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Add Clip", + FontSize = 20, + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Action = createClip + } + }); + } + + private void createClip() + { + // get layer + // create new clip + // fill in info + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/MenuButton.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/MenuButton.cs new file mode 100644 index 00000000..41e0a7cd --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/MenuButton.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. + +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI.Button; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; + +public partial class MenuButton : TextButton +{ + public MenuButton() + { + Stateful = false; + BackgroundColour = ThemeManager.Current[ThemeAttribute.Accent]; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs index c6381f34..60af3431 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/TimelineMenu.cs @@ -2,16 +2,18 @@ // 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.Input.Events; using osu.Framework.Platform; -using osuTK.Graphics; +using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; -public partial class TimelineMenu : VisibilityContainer +public abstract partial class TimelineMenu : VisibilityContainer { [Resolved] private GameHost host { get; set; } = null!; @@ -19,38 +21,30 @@ public partial class TimelineMenu : VisibilityContainer [Resolved] private ChatBoxScreen chatBoxScreen { get; set; } = null!; - private Container innerContainer = null!; + protected override FillFlowContainer Content { get; } - [BackgroundDependencyLoader] - private void load() + protected TimelineMenu() { - Child = innerContainer = new Container + InternalChild = new Container { Width = 200, AutoSizeAxes = Axes.Y, + BorderThickness = 2, + Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box { - Colour = Color4.Black, + Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.5f), RelativeSizeAxes = Axes.Both }, - new FillFlowContainer + Content = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = 300 - } - } + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 10) } } }; @@ -58,17 +52,17 @@ private void load() protected override void PopIn() { - Alpha = 1; + this.FadeInFromZero(100, Easing.OutQuad); - if (Position.Y + innerContainer.DrawHeight < host.Window.ClientSize.Height) - innerContainer.Origin = innerContainer.Anchor = Position.X + innerContainer.DrawWidth < host.Window.ClientSize.Width ? Anchor.TopLeft : Anchor.TopRight; + if (Position.Y + InternalChild.DrawHeight < host.Window.ClientSize.Height) + InternalChild.Origin = InternalChild.Anchor = Position.X + InternalChild.DrawWidth < host.Window.ClientSize.Width ? Anchor.TopLeft : Anchor.TopRight; else - innerContainer.Origin = innerContainer.Anchor = Position.X + innerContainer.DrawWidth < host.Window.ClientSize.Width ? Anchor.BottomLeft : Anchor.BottomRight; + InternalChild.Origin = InternalChild.Anchor = Position.X + InternalChild.DrawWidth < host.Window.ClientSize.Width ? Anchor.BottomLeft : Anchor.BottomRight; } protected override void PopOut() { - Alpha = 0; + this.FadeOutFromOne(100, Easing.OutQuad); } public void SetPosition(MouseDownEvent e) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index f4139768..08a1bfd9 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -1,17 +1,19 @@ // 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.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.Input.Events; using osuTK; +using osuTK.Input; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; -using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Clip; +using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Layer; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -26,15 +28,23 @@ public partial class TimelineEditor : Container private ChatBoxManager chatBoxManager { get; set; } = null!; [Resolved] - private TimelineMenu timelineMenu { get; set; } = null!; + private TimelineLayerMenu layerMenu { get; set; } = null!; + + [Resolved] + private TimelineClipMenu clipMenu { get; set; } = null!; private RectangularPositionSnapGrid snapping = null!; + private TimelineLayer layer5 = null!; + private TimelineLayer layer4 = null!; + private TimelineLayer layer3 = null!; + private TimelineLayer layer2 = null!; + private TimelineLayer layer1 = null!; + private TimelineLayer layer0 = null!; + [BackgroundDependencyLoader] private void load() { - TimelineLayer layer3; - Children = new Drawable[] { new Box @@ -62,9 +72,23 @@ private void load() new Dimension(), new Dimension(GridSizeMode.Absolute, 2), new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(), }, Content = new[] { + new Drawable[] + { + layer5 = new TimelineLayer() + }, + null, + new Drawable[] + { + layer4 = new TimelineLayer() + }, + null, new Drawable[] { layer3 = new TimelineLayer() @@ -72,42 +96,133 @@ private void load() null, new Drawable[] { - new TimelineLayer() + layer2 = new TimelineLayer() }, null, new Drawable[] { - new TimelineLayer() + layer1 = new TimelineLayer() }, null, new Drawable[] { - new TimelineLayer() + layer0 = new TimelineLayer() } } } } }; - chatBoxManager.Clips.ForEach(clip => layer3.Add(clip)); + chatBoxManager.Clips.BindCollectionChanged((_, e) => + { + if (e.NewItems == null) return; + + foreach (Clip newClip in e.NewItems) + { + switch (newClip.Priority.Value) + { + case 0: + layer0.Add(newClip); + break; + + case 1: + layer1.Add(newClip); + break; + + case 2: + layer2.Add(newClip); + break; + + case 3: + layer3.Add(newClip); + break; + + case 4: + layer4.Add(newClip); + break; + + case 5: + layer5.Add(newClip); + break; + + default: + throw new InvalidProgramException("Invalid priority"); + } + } + }, true); + + foreach (var clip in chatBoxManager.Clips) + { + clip.Priority.BindValueChanged(e => + { + getLayer(e.OldValue).Remove(clip); + getLayer(e.NewValue).Add(clip); + }); + } + } + + private TimelineLayer getLayer(int priority) + { + switch (priority) + { + case 0: + return layer0; + + case 1: + return layer1; + + case 2: + return layer2; + + case 3: + return layer3; + + case 4: + return layer4; + + case 5: + return layer5; + } + + return layer0; + } + + public void ShowLayerMenu(MouseDownEvent e) + { + clipMenu.Hide(); + layerMenu.Hide(); + layerMenu.SetPosition(e); + layerMenu.Show(); + } + + public void HideClipMenu() + { + clipMenu.Hide(); } - public void ShowMenu(MouseDownEvent e) + public void ShowClipMenu(Clip clip, MouseDownEvent e) { - timelineMenu.Hide(); - timelineMenu.SetPosition(e); - timelineMenu.Show(); + layerMenu.Hide(); + clipMenu.Hide(); + clipMenu.SetClip(clip); + clipMenu.SetPosition(e); + clipMenu.Show(); } protected override void LoadComplete() { - chatBoxManager.TimelineLength.BindValueChanged(e => snapping.Spacing = new Vector2(DrawWidth / (float)e.NewValue.TotalSeconds, 175), true); + chatBoxManager.TimelineLength.BindValueChanged(e => snapping.Spacing = new Vector2(DrawWidth / (float)e.NewValue.TotalSeconds, DrawHeight / 6), true); } - protected override bool OnClick(ClickEvent e) + protected override bool OnMouseDown(MouseDownEvent e) { - selectedClip.Value = null; - timelineMenu.Hide(); + if (e.Button == MouseButton.Left) + { + selectedClip.Value = null; + clipMenu.Hide(); + layerMenu.Hide(); + } + return true; } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index ecd4c2d4..03de3296 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -10,7 +11,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.Timeline; -public partial class TimelineLayer : Container +public partial class TimelineLayer : Container { [Resolved] private TimelineEditor timelineEditor { get; set; } = null!; @@ -21,15 +22,6 @@ private void load() RelativeSizeAxes = Axes.Both; Masking = true; CornerRadius = 10; - - Children = new Drawable[] - { - // new Box - // { - // Colour = ThemeManager.Current[ThemeAttribute.Dark], - // RelativeSizeAxes = Axes.Both, - // } - }; } public void Add(Clip clip) @@ -37,11 +29,19 @@ public void Add(Clip clip) Add(new DrawableClip(clip)); } + public void Remove(Clip clip) + { + Children.ForEach(child => + { + if (child.Clip == clip) Schedule(child.RemoveAndDisposeImmediately); + }); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Right) { - timelineEditor.ShowMenu(e); + timelineEditor.ShowLayerMenu(e); return true; } diff --git a/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs b/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs index 08213166..d38f18ff 100644 --- a/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs +++ b/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs @@ -77,7 +77,7 @@ private void generateGridLines(Direction direction, float startPosition, float e float currentPosition = startPosition; // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width * 7f; + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width * 2f; var generatedLines = new List(); diff --git a/VRCOSC.Game/Graphics/UI/Button/TextButton.cs b/VRCOSC.Game/Graphics/UI/Button/TextButton.cs index c3008930..d75aed67 100644 --- a/VRCOSC.Game/Graphics/UI/Button/TextButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/TextButton.cs @@ -8,7 +8,7 @@ namespace VRCOSC.Game.Graphics.UI.Button; -public sealed partial class TextButton : BasicButton +public partial class TextButton : BasicButton { private string text = string.Empty; From 3bd1ff2eab755050f3a9d209ef8975c88313454d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 14:52:39 +0100 Subject: [PATCH 22/59] Convert start and end to integers and finish clipping logic --- VRCOSC.Game/ChatBox/Clips/Clip.cs | 12 +++---- .../ChatBox/Metadata/ReadonlyTimeDisplay.cs | 4 +-- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 35 +++++++++++++------ .../ChatBox/Timeline/TimelineLayer.cs | 33 +++++++++++++++++ 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 2780aaf9..ba0aabcc 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -18,9 +18,9 @@ public class Clip public readonly BindableList AvailableVariables = new(); public readonly Dictionary States = new(); public readonly Dictionary Events = new(); - public readonly Bindable Start = new(); - public readonly Bindable End = new(0.5f); - public float Length => End.Value - Start.Value; + public readonly Bindable Start = new(); + public readonly Bindable End = new(30); + public int Length => End.Value - Start.Value; public Clip() { @@ -39,9 +39,9 @@ private void calculateAvailableVariables() public bool Intersects(Clip other) { - if (other.Start.Value >= Start.Value && other.Start.Value <= End.Value) return true; - if (other.End.Value >= Start.Value && other.End.Value <= End.Value) return true; - if (other.Start.Value <= Start.Value && other.End.Value >= End.Value) return true; + if (Start.Value >= other.Start.Value && Start.Value < other.End.Value) return true; + if (End.Value <= other.End.Value && End.Value > other.Start.Value) return true; + if (Start.Value < other.Start.Value && End.Value > other.End.Value) return true; return false; } diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs index eed87b86..e73d8d48 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs @@ -19,7 +19,7 @@ public partial class ReadonlyTimeDisplay : Container private ChatBoxManager chatBoxManager { get; set; } = null!; public required string Label { get; init; } - public required Bindable Current { get; init; } + public required Bindable Current { get; init; } private VRCOSCTextBox textBox = null!; @@ -69,7 +69,7 @@ protected override void LoadComplete() private void updateText() { - textBox.Text = (chatBoxManager.TimelineLength.Value.TotalSeconds * Current.Value).ToString("##0", CultureInfo.InvariantCulture); + textBox.Text = Current.Value.ToString("##0", CultureInfo.InvariantCulture); } private partial class LocalTextBox : VRCOSCTextBox diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index b81d1f66..2275ebf0 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osuTK.Graphics; using osuTK.Input; using VRCOSC.Game.ChatBox; @@ -28,6 +27,9 @@ public partial class DrawableClip : Container [Resolved] private TimelineEditor timelineEditor { get; set; } = null!; + [Resolved] + private TimelineLayer timelineLayer { get; set; } = null!; + [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; @@ -126,10 +128,13 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(cumulativeDrag) >= chatBoxManager.Resolution) { - var newStart = Clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); - var newEnd = Clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newStart = Clip.Start.Value + (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newEnd = Clip.End.Value + (float.IsNegative(cumulativeDrag) ? -1 : 1); + + var (lowerBound, _) = timelineLayer.GetBoundsNearestTo(Clip.Start.Value, false); + var (_, upperBound) = timelineLayer.GetBoundsNearestTo(Clip.End.Value, true); - if (Precision.AlmostBigger(newStart, 0) && Precision.AlmostBigger(-1 * newEnd, -1)) + if (newStart >= lowerBound && newEnd <= upperBound) { Clip.Start.Value = newStart; Clip.End.Value = newEnd; @@ -143,8 +148,8 @@ protected override void OnDrag(DragEvent e) private void updateSizeAndPosition() { - Width = Clip.Length; - X = Clip.Start.Value; + Width = Clip.Length * chatBoxManager.Resolution; + X = Clip.Start.Value * chatBoxManager.Resolution; } private partial class ResizeDetector : Container @@ -192,6 +197,9 @@ private partial class StartResizeDetector : ResizeDetector [Resolved] private DrawableClip parentDrawableClip { get; set; } = null!; + [Resolved] + private TimelineLayer timelineLayer { get; set; } = null!; + public StartResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { @@ -206,9 +214,11 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) { - var newStart = Clip.Start.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); + var newStart = Clip.Start.Value + (float.IsNegative(CumulativeDrag) ? -1 : 1); - if (Precision.AlmostBigger(newStart, 0) && Precision.AlmostBigger(-1 * newStart, -1 * Clip.End.Value)) + var (lowerBound, upperBound) = timelineLayer.GetBoundsNearestTo(float.IsNegative(CumulativeDrag) ? Clip.Start.Value : newStart, false); + + if (newStart >= lowerBound && newStart < upperBound) { Clip.Start.Value = newStart; } @@ -228,6 +238,9 @@ private partial class EndResizeDetector : ResizeDetector [Resolved] private DrawableClip parentDrawableClip { get; set; } = null!; + [Resolved] + private TimelineLayer timelineLayer { get; set; } = null!; + public EndResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { @@ -242,9 +255,11 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) { - var newEnd = Clip.End.Value + chatBoxManager.Resolution * (float.IsNegative(CumulativeDrag) ? -1 : 1); + var newEnd = Clip.End.Value + (float.IsNegative(CumulativeDrag) ? -1 : 1); + + var (lowerBound, upperBound) = timelineLayer.GetBoundsNearestTo(float.IsNegative(CumulativeDrag) ? newEnd : Clip.End.Value, true); - if (Precision.AlmostBigger(newEnd, Clip.Start.Value) && Precision.AlmostBigger(-1 * newEnd, -1)) + if (newEnd > lowerBound && newEnd <= upperBound) { Clip.End.Value = newEnd; } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index 03de3296..b7365954 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -1,6 +1,8 @@ // 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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -11,6 +13,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.Timeline; +[Cached] public partial class TimelineLayer : Container { [Resolved] @@ -37,6 +40,36 @@ public void Remove(Clip clip) }); } + public (int, int) GetBoundsNearestTo(int value, bool end) + { + var boundsList = new List(); + + Children.ForEach(child => + { + var clip = child.Clip; + + if (end) + { + if (clip.End.Value != value) boundsList.Add(clip.End.Value); + boundsList.Add(clip.Start.Value); + } + else + { + if (clip.Start.Value != value) boundsList.Add(clip.Start.Value); + boundsList.Add(clip.End.Value); + } + }); + + boundsList.Add(0); + boundsList.Add(60); + boundsList.Sort(); + + var lowerBound = boundsList.Last(bound => bound <= value); + var upperBound = boundsList.First(bound => bound >= value); + + return (lowerBound, upperBound); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Right) From b20dd605598ef98bb9e108b66cb59ae7b3bbc4bc Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 15:12:49 +0100 Subject: [PATCH 23/59] Allow clips to be added --- .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 19 ++++- .../ChatBox/Timeline/TimelineEditor.cs | 79 ++++++++++--------- .../ChatBox/Timeline/TimelineLayer.cs | 7 +- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index 2a230128..633a1d4f 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -4,11 +4,18 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using VRCOSC.Game.ChatBox; namespace VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Layer; public partial class TimelineLayerMenu : TimelineMenu { + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + public int XPos; + public TimelineLayer Layer; + [BackgroundDependencyLoader] private void load() { @@ -31,8 +38,14 @@ private void load() private void createClip() { - // get layer - // create new clip - // fill in info + var clip = new Game.ChatBox.Clips.Clip(); + + var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false); + + clip.Start.Value = lowerBound; + clip.End.Value = upperBound; + clip.Priority.Value = Layer.Priority; + + chatBoxManager.Clips.Add(clip); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 08a1bfd9..4989f9e0 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -81,32 +81,50 @@ private void load() { new Drawable[] { - layer5 = new TimelineLayer() + layer5 = new TimelineLayer + { + Priority = 5 + } }, null, new Drawable[] { - layer4 = new TimelineLayer() + layer4 = new TimelineLayer + { + Priority = 4 + } }, null, new Drawable[] { - layer3 = new TimelineLayer() + layer3 = new TimelineLayer + { + Priority = 3 + } }, null, new Drawable[] { - layer2 = new TimelineLayer() + layer2 = new TimelineLayer + { + Priority = 2 + } }, null, new Drawable[] { - layer1 = new TimelineLayer() + layer1 = new TimelineLayer + { + Priority = 1 + } }, null, new Drawable[] { - layer0 = new TimelineLayer() + layer0 = new TimelineLayer + { + Priority = 0 + } } } } @@ -148,50 +166,37 @@ private void load() default: throw new InvalidProgramException("Invalid priority"); } + + newClip.Priority.BindValueChanged(e => + { + getLayer(e.OldValue).Remove(newClip); + getLayer(e.NewValue).Add(newClip); + }); } }, true); - - foreach (var clip in chatBoxManager.Clips) - { - clip.Priority.BindValueChanged(e => - { - getLayer(e.OldValue).Remove(clip); - getLayer(e.NewValue).Add(clip); - }); - } } private TimelineLayer getLayer(int priority) { - switch (priority) + return priority switch { - case 0: - return layer0; - - case 1: - return layer1; - - case 2: - return layer2; - - case 3: - return layer3; - - case 4: - return layer4; - - case 5: - return layer5; - } - - return layer0; + 0 => layer0, + 1 => layer1, + 2 => layer2, + 3 => layer3, + 4 => layer4, + 5 => layer5, + _ => throw new InvalidOperationException($"No layer with priority {priority}") + }; } - public void ShowLayerMenu(MouseDownEvent e) + public void ShowLayerMenu(MouseDownEvent e, int xPos, TimelineLayer layer) { clipMenu.Hide(); layerMenu.Hide(); layerMenu.SetPosition(e); + layerMenu.XPos = xPos; + layerMenu.Layer = layer; layerMenu.Show(); } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index b7365954..4ccbcac1 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.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 System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -19,6 +20,8 @@ public partial class TimelineLayer : Container [Resolved] private TimelineEditor timelineEditor { get; set; } = null!; + public required int Priority { get; init; } + [BackgroundDependencyLoader] private void load() { @@ -74,7 +77,9 @@ protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Right) { - timelineEditor.ShowLayerMenu(e); + e.Target = this; + var xPos = (int)MathF.Floor(e.MousePosition.X / DrawWidth * 60); + timelineEditor.ShowLayerMenu(e, xPos, this); return true; } From 3830f6058620ea55bbbf88fdebfbaa24354a6173 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 15:22:12 +0100 Subject: [PATCH 24/59] Fix invalid clip spawns --- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 76 ++++++++++--------- .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 2 + 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index 2275ebf0..8a3957f4 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -47,54 +47,58 @@ private void load() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.X; - Masking = true; - CornerRadius = 10; - BorderColour = ThemeManager.Current[ThemeAttribute.Lighter]; - - updateSizeAndPosition(); - - selectedClip.BindValueChanged(e => - { - BorderThickness = Clip == e.NewValue ? 4 : 2; - }, true); SpriteText drawName; - Children = new Drawable[] + Child = new Container { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Light], - RelativeSizeAxes = Axes.Both - }, - new StartResizeDetector(Clip, v => v / Parent.DrawWidth) - { - RelativeSizeAxes = Axes.Y, - Width = 20 - }, - new EndResizeDetector(Clip, v => v / Parent.DrawWidth) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 20 - }, - new Container + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + BorderColour = ThemeManager.Current[ThemeAttribute.Lighter], + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - Children = new Drawable[] + new Box { - drawName = new SpriteText + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both + }, + new StartResizeDetector(Clip, v => v / Parent.DrawWidth) + { + RelativeSizeAxes = Axes.Y, + Width = 20 + }, + new EndResizeDetector(Clip, v => v / Parent.DrawWidth) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 20 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = FrameworkFont.Regular.With(size: 20) + drawName = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = FrameworkFont.Regular.With(size: 20) + } } } } }; + updateSizeAndPosition(); + + selectedClip.BindValueChanged(e => + { + ((Container)Child).BorderThickness = Clip == e.NewValue ? 4 : 2; + }, true); + Clip.Name.BindValueChanged(e => drawName.Text = e.NewValue, true); } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index 633a1d4f..83cc7675 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -47,5 +47,7 @@ private void createClip() clip.Priority.Value = Layer.Priority; chatBoxManager.Clips.Add(clip); + + Hide(); } } From 365b08b7c43a7777c60ae213c820a3ced0d9c699 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 8 Apr 2023 16:07:05 +0100 Subject: [PATCH 25/59] Add basis for module selector and replace grid generator --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 6 +- .../SelectedClipModuleSelector.cs | 85 +++++++++++- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 8 +- .../ChatBox/Timeline/TimelineEditor.cs | 43 ++++-- .../RectangularPositionSnappingGrid.cs | 130 ------------------ 5 files changed, 123 insertions(+), 149 deletions(-) delete mode 100644 VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index f2fdcfc0..30b2dabc 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -21,10 +21,12 @@ public ChatBoxManager() { for (var i = 0; i < 6; i++) { - Clips.Add(new Clip + Clip clip; + Clips.Add(clip = new Clip { - Priority = { Value = i } + Priority = { Value = i }, }); + clip.AssociatedModules.Add("Test " + i); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index d43bd28d..9cf5dff6 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -2,22 +2,99 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipModuleSelector : Container { + [Resolved] + private Bindable selectedClip { get; set; } = null!; + + private FillFlowContainer moduleFlow = null!; + [BackgroundDependencyLoader] private void load() { - Child = new Box + Children = new Drawable[] { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 30)) + { + RelativeSizeAxes = Axes.Both, + Text = "Select ChatBox-enabled\nModules", + TextAnchor = Anchor.TopCentre + } + }, + null, + new Drawable[] + { + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = moduleFlow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + } + } + } + } + } + } }; + + selectedClip.BindValueChanged(e => + { + if (e.NewValue is null) + { + e.OldValue?.AssociatedModules.UnbindBindings(); + return; + } + + e.NewValue.AssociatedModules.BindCollectionChanged((_, e) => + { + if (e.NewItems is null) return; + + moduleFlow.Clear(); + + foreach (string newModule in e.NewItems) + { + moduleFlow.Add(new SpriteText + { + Font = FrameworkFont.Regular.With(size: 20), + Text = newModule + }); + } + }, true); + }, true); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index 8a3957f4..db12a73c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -132,8 +132,8 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(cumulativeDrag) >= chatBoxManager.Resolution) { - var newStart = Clip.Start.Value + (float.IsNegative(cumulativeDrag) ? -1 : 1); - var newEnd = Clip.End.Value + (float.IsNegative(cumulativeDrag) ? -1 : 1); + var newStart = Clip.Start.Value + Math.Sign(cumulativeDrag); + var newEnd = Clip.End.Value + Math.Sign(cumulativeDrag); var (lowerBound, _) = timelineLayer.GetBoundsNearestTo(Clip.Start.Value, false); var (_, upperBound) = timelineLayer.GetBoundsNearestTo(Clip.End.Value, true); @@ -218,7 +218,7 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) { - var newStart = Clip.Start.Value + (float.IsNegative(CumulativeDrag) ? -1 : 1); + var newStart = Clip.Start.Value + Math.Sign(CumulativeDrag); var (lowerBound, upperBound) = timelineLayer.GetBoundsNearestTo(float.IsNegative(CumulativeDrag) ? Clip.Start.Value : newStart, false); @@ -259,7 +259,7 @@ protected override void OnDrag(DragEvent e) if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) { - var newEnd = Clip.End.Value + (float.IsNegative(CumulativeDrag) ? -1 : 1); + var newEnd = Clip.End.Value + Math.Sign(CumulativeDrag); var (lowerBound, upperBound) = timelineLayer.GetBoundsNearestTo(float.IsNegative(CumulativeDrag) ? newEnd : Clip.End.Value, true); diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 4989f9e0..7620a25f 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -33,14 +34,13 @@ public partial class TimelineEditor : Container [Resolved] private TimelineClipMenu clipMenu { get; set; } = null!; - private RectangularPositionSnapGrid snapping = null!; - private TimelineLayer layer5 = null!; private TimelineLayer layer4 = null!; private TimelineLayer layer3 = null!; private TimelineLayer layer2 = null!; private TimelineLayer layer1 = null!; private TimelineLayer layer0 = null!; + private Container gridGenerator = null!; [BackgroundDependencyLoader] private void load() @@ -52,9 +52,10 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Mid], RelativeSizeAxes = Axes.Both }, - snapping = new RectangularPositionSnapGrid(Vector2.Zero) + gridGenerator = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Position = new Vector2(-2.5f) }, new Container { @@ -174,6 +175,35 @@ private void load() }); } }, true); + + generateGrid(); + } + + private void generateGrid() + { + for (var i = 0; i < 60; i++) + { + gridGenerator.Add(new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.5f), + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 5, + X = (chatBoxManager.Resolution * i) + }); + } + + for (var i = 0; i < 6; i++) + { + gridGenerator.Add(new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.5f), + RelativeSizeAxes = Axes.X, + RelativePositionAxes = Axes.Y, + Height = 5, + Y = (DrawHeight / 6 * i) + }); + } } private TimelineLayer getLayer(int priority) @@ -214,11 +244,6 @@ public void ShowClipMenu(Clip clip, MouseDownEvent e) clipMenu.Show(); } - protected override void LoadComplete() - { - chatBoxManager.TimelineLength.BindValueChanged(e => snapping.Spacing = new Vector2(DrawWidth / (float)e.NewValue.TotalSeconds, DrawHeight / 6), true); - } - protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Left) diff --git a/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs b/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs deleted file mode 100644 index d38f18ff..00000000 --- a/VRCOSC.Game/Graphics/RectangularPositionSnappingGrid.cs +++ /dev/null @@ -1,130 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; -using osu.Framework.Utils; -using osuTK; - -namespace VRCOSC.Game.Graphics; - -// Taken from https://github.com/ppy/osu/blob/1122ee967c346b2b3454ac0a19a928e723717aff/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs -public partial class RectangularPositionSnapGrid : CompositeDrawable -{ - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition { get; } - - private Vector2 spacing = Vector2.One; - - /// - /// The spacing between grid lines of this . - /// - public Vector2 Spacing - { - get => spacing; - set - { - if (spacing.X <= 0 || spacing.Y <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - gridCache.Invalidate(); - } - } - - private readonly LayoutValue gridCache = new(Invalidation.RequiredParentSizeToFit); - - public RectangularPositionSnapGrid(Vector2 startPosition) - { - StartPosition = startPosition; - - AddLayout(gridCache); - } - - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - createContent(); - gridCache.Validate(); - } - } - - private void createContent() - { - var drawSize = DrawSize; - - generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); - generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); - - generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); - generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); - } - - private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) - { - int index = 0; - float currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width * 2f; - - var generatedLines = new List(); - - while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0)) - { - var gridLine = new Box - { - Colour = Colour4.Black, - Alpha = 0.1f - }; - - if (direction == Direction.Horizontal) - { - gridLine.Origin = Anchor.CentreLeft; - gridLine.RelativeSizeAxes = Axes.X; - gridLine.Height = lineWidth; - gridLine.Y = currentPosition; - } - else - { - gridLine.Origin = Anchor.TopCentre; - gridLine.RelativeSizeAxes = Axes.Y; - gridLine.Width = lineWidth; - gridLine.X = currentPosition; - } - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 1f; - generatedLines.Last().Alpha = 1f; - - AddRangeInternal(generatedLines); - } - - public Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = original - StartPosition; - Vector2 offset = Vector2.Divide(relativeToStart, Spacing); - Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - - return StartPosition + Vector2.Multiply(roundedOffset, Spacing); - } -} From c2888c47ac6a6aa7558d348542e8a19926cca073 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 9 Apr 2023 09:23:48 +0100 Subject: [PATCH 26/59] Create DrawableAssociatedModule --- .../SelectedClip/DrawableAssociatedModule.cs | 50 +++++++++++++++++++ .../SelectedClipModuleSelector.cs | 47 +++++++++++------ .../Graphics/ChatBox/Timeline/DrawableClip.cs | 2 + .../ChatBox/Timeline/TimelineEditor.cs | 30 +---------- .../Timeline/TimelineMetadataEditor.cs | 2 - 5 files changed, 84 insertions(+), 47 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs new file mode 100644 index 00000000..6197a481 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs @@ -0,0 +1,50 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using VRCOSC.Game.Graphics.UI.Button; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class DrawableAssociatedModule : Container +{ + public required string ModuleName { get; init; } + public readonly BindableBool State = new(); + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Child = new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = State + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 20)) + { + RelativeSizeAxes = Axes.Both, + Text = ModuleName + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index 9cf5dff6..c338f95a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -10,6 +10,7 @@ using osuTK; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -18,6 +19,9 @@ public partial class SelectedClipModuleSelector : Container [Resolved] private Bindable selectedClip { get; set; } = null!; + [Resolved] + private GameManager gameManager { get; set; } = null!; + private FillFlowContainer moduleFlow = null!; [BackgroundDependencyLoader] @@ -72,29 +76,40 @@ private void load() } }; - selectedClip.BindValueChanged(e => + foreach (var module in gameManager.ModuleManager) { - if (e.NewValue is null) + DrawableAssociatedModule drawableAssociatedModule; + + moduleFlow.Add(drawableAssociatedModule = new DrawableAssociatedModule { - e.OldValue?.AssociatedModules.UnbindBindings(); - return; - } + ModuleName = module.Title + }); - e.NewValue.AssociatedModules.BindCollectionChanged((_, e) => + drawableAssociatedModule.State.BindValueChanged(e => { - if (e.NewItems is null) return; + if (e.NewValue) + { + selectedClip.Value!.AssociatedModules.Add(module.Title); + } + else + { + selectedClip.Value!.AssociatedModules.Remove(module.Title); + } + }); + } - moduleFlow.Clear(); + selectedClip.BindValueChanged(e => + { + if (e.NewValue is null) return; - foreach (string newModule in e.NewItems) + foreach (string newModule in e.NewValue.AssociatedModules) + { + moduleFlow.Add(new DrawableAssociatedModule { - moduleFlow.Add(new SpriteText - { - Font = FrameworkFont.Regular.With(size: 20), - Text = newModule - }); - } - }, true); + ModuleName = newModule, + State = { Value = true } + }); + } }, true); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index db12a73c..d0b6a7a5 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -100,6 +100,8 @@ private void load() }, true); Clip.Name.BindValueChanged(e => drawName.Text = e.NewValue, true); + + Clip.Enabled.BindValueChanged(e => Child.FadeTo(e.NewValue ? 1 : 0.5f), true); } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 7620a25f..8328eeab 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -138,35 +138,7 @@ private void load() foreach (Clip newClip in e.NewItems) { - switch (newClip.Priority.Value) - { - case 0: - layer0.Add(newClip); - break; - - case 1: - layer1.Add(newClip); - break; - - case 2: - layer2.Add(newClip); - break; - - case 3: - layer3.Add(newClip); - break; - - case 4: - layer4.Add(newClip); - break; - - case 5: - layer5.Add(newClip); - break; - - default: - throw new InvalidProgramException("Invalid priority"); - } + getLayer(newClip.Priority.Value).Add(newClip); newClip.Priority.BindValueChanged(e => { diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs index fca21c10..156f03ab 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs @@ -20,8 +20,6 @@ public partial class TimelineMetadataEditor : Container [BackgroundDependencyLoader] private void load() { - // TODO - Add snap resolution - Children = new Drawable[] { new Box From 645308f8e40a46f89a174fac2149f91a0f4a8f76 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 9 Apr 2023 10:00:40 +0100 Subject: [PATCH 27/59] Add basis for SelectedClipStateEditor --- .../SelectedClip/DrawableAssociatedModule.cs | 55 ++++++++++------ .../SelectedClipModuleSelector.cs | 58 ++++++++-------- .../SelectedClip/SelectedClipStateEditor.cs | 66 +++++++++++++++++-- VRCOSC.Game/Modules/Module.cs | 3 +- 4 files changed, 127 insertions(+), 55 deletions(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs index 6197a481..8e292cb5 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osuTK; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -17,32 +18,44 @@ public partial class DrawableAssociatedModule : Container [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.X; + Height = 50; + Children = new Drawable[] { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Child = new ToggleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - State = State - } - }, - new Container + new FillFlowContainer { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 20)) + Spacing = new Vector2(10, 0), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Text = ModuleName + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Child = new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = State + } + }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Child = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 20)) + { + RelativeSizeAxes = Axes.Both, + TextAnchor = Anchor.CentreLeft, + Text = ModuleName + } + } } } }; diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index c338f95a..54e95c7b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -6,10 +6,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -22,7 +22,7 @@ public partial class SelectedClipModuleSelector : Container [Resolved] private GameManager gameManager { get; set; } = null!; - private FillFlowContainer moduleFlow = null!; + private FillFlowContainer moduleFlow = null!; [BackgroundDependencyLoader] private void load() @@ -37,7 +37,7 @@ private void load() new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), + Padding = new MarginPadding(10), Child = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -61,13 +61,16 @@ private void load() null, new Drawable[] { - new BasicScrollContainer + new VRCOSCScrollContainer { RelativeSizeAxes = Axes.Both, - Child = moduleFlow = new FillFlowContainer + ClampExtension = 20, + Child = moduleFlow = new FillFlowContainer { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5) + Spacing = new Vector2(0, 20) } } } @@ -76,38 +79,35 @@ private void load() } }; - foreach (var module in gameManager.ModuleManager) + selectedClip.BindValueChanged(e => { - DrawableAssociatedModule drawableAssociatedModule; + if (e.NewValue is null) return; - moduleFlow.Add(drawableAssociatedModule = new DrawableAssociatedModule - { - ModuleName = module.Title - }); + moduleFlow.Clear(); - drawableAssociatedModule.State.BindValueChanged(e => + foreach (var module in gameManager.ModuleManager) { - if (e.NewValue) + DrawableAssociatedModule drawableAssociatedModule; + + moduleFlow.Add(drawableAssociatedModule = new DrawableAssociatedModule { - selectedClip.Value!.AssociatedModules.Add(module.Title); - } - else + ModuleName = module.Title + }); + + foreach (string moduleName in e.NewValue.AssociatedModules) { - selectedClip.Value!.AssociatedModules.Remove(module.Title); + if (module.SerialisedName == moduleName) + { + drawableAssociatedModule.State.Value = true; + } } - }); - } - selectedClip.BindValueChanged(e => - { - if (e.NewValue is null) return; - - foreach (string newModule in e.NewValue.AssociatedModules) - { - moduleFlow.Add(new DrawableAssociatedModule + drawableAssociatedModule.State.BindValueChanged(e => { - ModuleName = newModule, - State = { Value = true } + if (e.NewValue) + selectedClip.Value!.AssociatedModules.Add(module.SerialisedName); + else + selectedClip.Value!.AssociatedModules.Remove(module.SerialisedName); }); } }, true); diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs index 30ea6dc3..60fcf1e9 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs @@ -2,22 +2,80 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipStateEditor : Container { + [Resolved] + private Bindable selectedClip { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { - Child = new Box + Children = new Drawable[] { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.25f), + }, + Content = new[] + { + new Drawable?[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Darker], + RelativeSizeAxes = Axes.Both + }, + } + }, + null, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Darker], + RelativeSizeAxes = Axes.Both + }, + } + } + } + } + } + } }; } } diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index 15f770e2..71613553 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -49,7 +49,8 @@ public abstract class Module : IComparable private bool IsEnabled => Enabled.Value; private bool ShouldUpdate => DeltaUpdate != TimeSpan.MaxValue; - internal string FileName => @$"{GetType().Name}.ini"; + internal string SerialisedName => GetType().Name; + internal string FileName => @$"{SerialisedName}.ini"; protected bool IsStarting => State.Value == ModuleState.Starting; protected bool HasStarted => State.Value == ModuleState.Started; From 53c676d3f901149c0ba711744cc810e960f5b407 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 9 Apr 2023 11:21:08 +0100 Subject: [PATCH 28/59] Remove ChatBoxV2 and convert all modules to V3 --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 18 ++ VRCOSC.Game/ChatBox/ChatBoxVariable.cs | 3 +- VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs | 29 +++ VRCOSC.Game/Modules/ChatBoxInterface.cs | 167 ------------------ VRCOSC.Game/Modules/ChatBoxModule.cs | 106 ----------- VRCOSC.Game/Modules/GameManager.cs | 18 +- VRCOSC.Game/Modules/Manager/ModuleManager.cs | 5 - VRCOSC.Game/Modules/Module.cs | 7 +- VRCOSC.Modules/AFK/AFKModule.cs | 47 +++-- .../ChatBoxText/ChatBoxTextModule.cs | 64 ++++--- VRCOSC.Modules/Clock/ClockModule.cs | 47 +++-- .../HardwareStats/HardwareStatsModule.cs | 77 ++++---- VRCOSC.Modules/Heartrate/HeartRateModule.cs | 28 ++- VRCOSC.Modules/Media/MediaModule.cs | 63 +++---- .../OpenVR/OpenVRStatisticsModule.cs | 63 ++++--- VRCOSC.Modules/Weather/WeatherModule.cs | 47 ++--- 16 files changed, 310 insertions(+), 479 deletions(-) create mode 100644 VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs delete mode 100644 VRCOSC.Game/Modules/ChatBoxInterface.cs delete mode 100644 VRCOSC.Game/Modules/ChatBoxModule.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 30b2dabc..e05f322a 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -13,6 +13,8 @@ public class ChatBoxManager { public readonly BindableList Clips = new(); + public readonly Dictionary Variables = new(); + public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); public float Resolution => 1f / (float)TimelineLength.Value.TotalSeconds; @@ -22,6 +24,7 @@ public ChatBoxManager() for (var i = 0; i < 6; i++) { Clip clip; + Clips.Add(clip = new Clip { Priority = { Value = i }, @@ -42,5 +45,20 @@ public bool SetPriority(Clip clip, int priority) return true; } + public void RegisterVariable(string lookup, string name, string format) + { + Variables.Add(lookup, new ChatBoxVariable + { + Lookup = lookup, + Name = name, + Format = format + }); + } + + public void SetVariable(string lookup, string? value) + { + Variables[lookup].Value = value; + } + public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); } diff --git a/VRCOSC.Game/ChatBox/ChatBoxVariable.cs b/VRCOSC.Game/ChatBox/ChatBoxVariable.cs index 84494794..37b4e4d3 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxVariable.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxVariable.cs @@ -9,6 +9,7 @@ namespace VRCOSC.Game.ChatBox; public class ChatBoxVariable { public required string Lookup { get; init; } + public required string Name { get; init; } public required string Format { get; init; } - public required string Description { get; init; } + public string? Value { get; set; } } diff --git a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs new file mode 100644 index 00000000..1e544bec --- /dev/null +++ b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs @@ -0,0 +1,29 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; + +namespace VRCOSC.Game.Modules.ChatBox; + +public abstract class ChatBoxModule : Module +{ + protected void CreateVariable(Enum lookup, string name, string format) + { + ChatBoxManager.RegisterVariable(SerialisedName + "-" + lookup.ToLookup(), name, format); + } + + protected void SetVariableValue(Enum lookup, string? value) + { + ChatBoxManager.SetVariable(SerialisedName + "-" + lookup.ToLookup(), value); + } + + protected void CreateState(Enum lookup, string name, string defaultFormat) + { + + } + + protected void ChangeStateTo(Enum lookup) + { + + } +} diff --git a/VRCOSC.Game/Modules/ChatBoxInterface.cs b/VRCOSC.Game/Modules/ChatBoxInterface.cs deleted file mode 100644 index 86d5bb4d..00000000 --- a/VRCOSC.Game/Modules/ChatBoxInterface.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.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using VRCOSC.Game.OSC.VRChat; - -namespace VRCOSC.Game.Modules; - -public class ChatBoxInterface -{ - private readonly ConcurrentQueue timedQueue = new(); - private readonly ConcurrentDictionary alwaysDict = new(); - private readonly VRChatOscClient oscClient; - private readonly IBindable resetMilli; - - private ChatBoxData? currentData; - private DateTimeOffset? sendReset; - private DateTimeOffset sendExpire; - private bool alreadyClear; - private bool running; - - private bool sendEnabled; - - public bool SendEnabled - { - get => sendEnabled; - set - { - sendEnabled = value; - if (!sendEnabled) clear(); - } - } - - public ChatBoxInterface(VRChatOscClient oscClient, IBindable resetMilli) - { - this.oscClient = oscClient; - this.resetMilli = resetMilli; - } - - public void SetTyping(bool typing) - { - oscClient.SendValue(VRChatOscConstants.ADDRESS_CHATBOX_TYPING, typing); - } - - public void Initialise() - { - currentData = null; - sendReset = null; - alreadyClear = true; - SendEnabled = true; - sendExpire = DateTimeOffset.Now; - timedQueue.Clear(); - alwaysDict.Clear(); - running = true; - } - - public void Shutdown() - { - running = false; - clear(); - } - - private void clear() - { - sendText(string.Empty); - alreadyClear = true; - } - - public DateTimeOffset SetText(string? text, int priority, TimeSpan displayLength) - { - var data = new ChatBoxData - { - Text = text, - DisplayLength = displayLength - }; - - // ChatBoxMode.Always - if (displayLength == TimeSpan.Zero) - { - alwaysDict[priority] = data; - return DateTimeOffset.Now; - } - - // ChatBoxMode.Timed - - if (text is null) return DateTimeOffset.Now; - - timedQueue.Enqueue(new ChatBoxData - { - Text = text, - DisplayLength = displayLength - }); - - var closestNextSendTime = DateTimeOffset.Now; - timedQueue.ForEach(d => closestNextSendTime += d.DisplayLength); - return closestNextSendTime; - } - - public void Update() - { - if (!running) return; - - switch (timedQueue.IsEmpty) - { - case true when sendExpire < DateTimeOffset.Now: - { - var validAlwaysData = alwaysDict.Where(data => data.Value.Text is not null).ToImmutableSortedDictionary(); - currentData = validAlwaysData.IsEmpty ? null : validAlwaysData.Last().Value; - break; - } - - case false: - { - if (sendExpire < DateTimeOffset.Now && timedQueue.TryDequeue(out var data)) - { - currentData = data; - sendExpire = DateTimeOffset.Now + data.DisplayLength; - } - - break; - } - } - - trySendText(); - } - - private void trySendText() - { - if (sendReset is not null && sendReset <= DateTimeOffset.Now) sendReset = null; - if (sendReset is not null) return; - - if (currentData is null) - { - if (!alreadyClear) - { - clear(); - sendReset = DateTimeOffset.Now + TimeSpan.FromMilliseconds(resetMilli.Value); - } - - return; - } - - alreadyClear = false; - - if (currentData.Text is null) return; - - if (sendEnabled) sendText(currentData.Text); - sendReset = DateTimeOffset.Now + TimeSpan.FromMilliseconds(resetMilli.Value); - } - - private void sendText(string text) - { - oscClient.SendValues(VRChatOscConstants.ADDRESS_CHATBOX_INPUT, new List { text, true, false }); - } - - private class ChatBoxData - { - public string? Text { get; init; } - public TimeSpan DisplayLength { get; init; } - } -} diff --git a/VRCOSC.Game/Modules/ChatBoxModule.cs b/VRCOSC.Game/Modules/ChatBoxModule.cs deleted file mode 100644 index 58fe398c..00000000 --- a/VRCOSC.Game/Modules/ChatBoxModule.cs +++ /dev/null @@ -1,106 +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; - -namespace VRCOSC.Game.Modules; - -public abstract partial class ChatBoxModule : Module -{ - protected virtual bool DefaultChatBoxDisplay => true; - protected virtual IEnumerable ChatBoxFormatValues => Array.Empty(); - protected virtual string DefaultChatBoxFormat => string.Empty; - protected virtual int ChatBoxPriority => 0; - - private DateTimeOffset nextSendTime; - - 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); - - 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 void OnModuleStart() - { - nextSendTime = DateTimeOffset.Now; - } - - protected override void Update() - { - if (State.Value == ModuleState.Started) trySend(); - } - - private void trySend() - { - if (!GetSetting(ChatBoxSetting.ChatBoxDisplay)) return; - if (GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Timed && nextSendTime > DateTimeOffset.Now) return; - - var text = GetChatBoxText(); - - var displayTimerTimeSpan = TimeSpan.FromSeconds(GetSetting(ChatBoxSetting.ChatBoxTimer)); - var displayLengthTimeSpan = GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Timed ? TimeSpan.FromSeconds(GetSetting(ChatBoxSetting.ChatBoxLength)) : TimeSpan.Zero; - - var closestNextSendTime = SetChatBoxText(text, displayLengthTimeSpan); - - if (GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Always) - { - nextSendTime = DateTimeOffset.Now; - return; - } - - // Used for late loading modules - // I.E. HardwareStats doesn't start producing values for a few seconds but we want to queue it ASAP once it does - // so that the timing isn't messed up - if (GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Timed && closestNextSendTime <= DateTimeOffset.Now) - { - nextSendTime = DateTimeOffset.Now; - return; - } - - if (closestNextSendTime >= DateTimeOffset.Now + displayTimerTimeSpan) - nextSendTime = closestNextSendTime; - else - nextSendTime = closestNextSendTime + (displayTimerTimeSpan - displayLengthTimeSpan); - } - - /// - /// Called to gather text to be put into the ChatBox. - /// Text is allowed to be empty. Null indicates that the module is in an invalid state to send text and will be denied ChatBox time. - /// - /// - protected abstract string? GetChatBoxText(); - - protected DateTimeOffset SetChatBoxText(string? text, TimeSpan displayLength) => ChatBoxInterface.SetText(text, ChatBoxPriority, displayLength); - - protected void SetChatBoxTyping(bool typing) => ChatBoxInterface.SetTyping(typing); - - protected enum ChatBoxSetting - { - ChatBoxDisplay, - ChatBoxFormat, - ChatBoxMode, - ChatBoxTimer, - ChatBoxLength - } - - private enum ChatBoxMode - { - Timed, - Always - } -} diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index a9ef75a1..0d9b41fb 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using Valve.VR; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Config; using VRCOSC.Game.Graphics.Notifications; using VRCOSC.Game.Modules.Avatar; @@ -59,6 +60,9 @@ public partial class GameManager : Component [Resolved] private IVRCOSCSecrets secrets { get; set; } = null!; + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + private Bindable autoStartStop = null!; private bool hasAutoStarted; private readonly List oscDataCache = new(); @@ -70,7 +74,7 @@ public partial class GameManager : Component public OSCRouter OSCRouter = null!; public Player Player = null!; public OVRClient OVRClient = null!; - public ChatBoxInterface ChatBoxInterface = null!; + public ChatBoxManager ChatBoxManager => chatBoxManager; public AvatarConfig? AvatarConfig; [BackgroundDependencyLoader] @@ -89,8 +93,6 @@ private void load() }); OVRHelper.OnError += m => Logger.Log($"[OpenVR] {m}"); - ChatBoxInterface = new ChatBoxInterface(VRChatOscClient, configManager.GetBindable(VRCOSCSetting.ChatBoxTimeSpan)); - setupModules(); } @@ -114,7 +116,7 @@ protected override void Update() ModuleManager.Update(); - ChatBoxInterface.Update(); + //ChatBoxInterface.Update(); } protected override void LoadComplete() @@ -161,7 +163,7 @@ private void handleOscDataCache() switch (data.ParameterName) { case @"VRCOSC/Controls/ChatBox": - ChatBoxInterface.SendEnabled = (bool)data.ParameterValue; + //ChatBoxInterface.SendEnabled = (bool)data.ParameterValue; break; } } @@ -207,7 +209,7 @@ private async Task startAsync() VRChatOscClient.Enable(OscClientFlag.Send); Player.Initialise(); - ChatBoxInterface.Initialise(); + //ChatBoxInterface.Initialise(); sendControlValues(); ModuleManager.Start(); VRChatOscClient.Enable(OscClientFlag.Receive); @@ -236,7 +238,7 @@ private async Task stopAsync() await OSCRouter.Disable(); ModuleManager.Stop(); - ChatBoxInterface.Shutdown(); + //ChatBoxInterface.Shutdown(); Player.ResetAll(); await VRChatOscClient.Disable(OscClientFlag.Send); @@ -307,7 +309,7 @@ private void checkForVRChat() private void sendControlValues() { - VRChatOscClient.SendValue(@$"{VRChatOscConstants.ADDRESS_AVATAR_PARAMETERS_PREFIX}/VRCOSC/Controls/ChatBox", ChatBoxInterface.SendEnabled); + //VRChatOscClient.SendValue(@$"{VRChatOscConstants.ADDRESS_AVATAR_PARAMETERS_PREFIX}/VRCOSC/Controls/ChatBox", ChatBoxInterface.SendEnabled); } } diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index c217151c..8f6356e5 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -87,11 +87,6 @@ public void Start() public void Update() { scheduler.Update(); - - foreach (var module in modules) - { - module.internalUpdate(); - } } public void Stop() diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index 71613553..c0349c4d 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Platform; using osu.Framework.Threading; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Modules.Avatar; using VRCOSC.Game.OpenVR; using VRCOSC.Game.OSC.VRChat; @@ -30,7 +31,7 @@ public abstract class Module : IComparable protected Player Player => GameManager.Player; protected OVRClient OVRClient => GameManager.OVRClient; protected VRChatOscClient OscClient => GameManager.VRChatOscClient; - protected ChatBoxInterface ChatBoxInterface => GameManager.ChatBoxInterface; + protected ChatBoxManager ChatBoxManager => GameManager.ChatBoxManager; protected Bindable State = new(ModuleState.Stopped); protected AvatarConfig? AvatarConfig => GameManager.AvatarConfig; @@ -140,10 +141,6 @@ internal void Start() if (ShouldUpdateImmediately) OnModuleUpdate(); } - // TODO - Proxy for now until ChatBoxV3 is decoupled from ChatBoxModule - internal void internalUpdate() => Update(); - protected virtual void Update() { } - internal void Stop() { if (!IsEnabled) return; diff --git a/VRCOSC.Modules/AFK/AFKModule.cs b/VRCOSC.Modules/AFK/AFKModule.cs index 52a32a30..e8bb6d31 100644 --- a/VRCOSC.Modules/AFK/AFKModule.cs +++ b/VRCOSC.Modules/AFK/AFKModule.cs @@ -1,7 +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 VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; namespace VRCOSC.Modules.AFK; @@ -12,41 +12,52 @@ public class AFKModule : ChatBoxModule public override string Author => "VolcanicArts"; public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(1.5f); - protected override int ChatBoxPriority => 4; - protected override IEnumerable ChatBoxFormatValues => new[] { "%duration%" }; - protected override string DefaultChatBoxFormat => "AFK for %duration%"; - private bool isAFK; private DateTime? afkBegan; - protected override void OnModuleStart() + protected override void CreateAttributes() { - isAFK = false; - afkBegan = null; + CreateVariable(AFKModuleVariable.Duration, "Duration", "{duration}"); + + CreateState(AFKModuleState.AFK, "AFK", "AFK for {duration}"); + CreateState(AFKModuleState.NotAFK, "Not AFK", string.Empty); } - protected override string? GetChatBoxText() + protected override void OnModuleStart() { - if (afkBegan is null) return null; - - return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace("%duration%", (DateTime.Now - afkBegan.Value).ToString(@"hh\:mm\:ss")); + afkBegan = null; } protected override void OnModuleUpdate() { - if (Player.AFK is null) return; + if (Player.AFK is null) + { + ChangeStateTo(AFKModuleState.NotAFK); + return; + } - if (Player.AFK.Value && !isAFK) + if (Player.AFK.Value && afkBegan is null) { afkBegan = DateTime.Now; - isAFK = true; } - if (!Player.AFK.Value && isAFK) + if (!Player.AFK.Value && afkBegan is not null) { afkBegan = null; - isAFK = false; } + + SetVariableValue(AFKModuleVariable.Duration, afkBegan is null ? null : (DateTime.Now - afkBegan.Value).ToString(@"hh\:mm\:ss")); + ChangeStateTo(afkBegan is null ? AFKModuleState.NotAFK : AFKModuleState.AFK); + } + + private enum AFKModuleVariable + { + Duration + } + + private enum AFKModuleState + { + AFK, + NotAFK } } diff --git a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs index 3b44e366..35f13803 100644 --- a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs +++ b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs @@ -1,11 +1,11 @@ // 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.Modules; +using VRCOSC.Game.Modules.ChatBox; namespace VRCOSC.Modules.ChatBoxText; -public partial class ChatBoxTextModule : ChatBoxModule +public class ChatBoxTextModule : ChatBoxModule { public override string Title => "ChatBox Text"; public override string Description => "Display custom text in the ChatBox"; @@ -13,26 +13,19 @@ public partial class ChatBoxTextModule : ChatBoxModule public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(1.5f); protected override bool ShouldUpdateImmediately => false; - protected override int ChatBoxPriority => 1; private int index; - protected override string GetChatBoxText() + protected override void CreateAttributes() { - var text = GetSetting(ChatBoxTextSetting.ChatBoxText); - - if (!GetSetting(ChatBoxTextSetting.Animate)) return text; - - var splitter = GetSetting(ChatBoxTextSetting.Splitter); - - var tickerText = $"{text}{splitter}{text}"; - var maxLength = Math.Min(GetSetting(ChatBoxTextSetting.MaxLength), text.Length); - - if (index > text.Length + splitter.Length - 1) index = 0; - - tickerText = tickerText[index..(maxLength + index)]; + CreateSetting(ChatBoxTextSetting.ChatBoxText, "ChatBox Text", "What text should be displayed in the ChatBox?", string.Empty); + CreateSetting(ChatBoxTextSetting.Animate, "Animate", "Should the text animate like a ticker tape?", false); + CreateSetting(ChatBoxTextSetting.ScrollSpeed, "Scroll Speed", "How fast should the text scroll? Measured in characters per update.", 1, () => GetSetting(ChatBoxTextSetting.Animate)); + CreateSetting(ChatBoxTextSetting.Splitter, "Splitter", "The splitter that goes between loops of the text", " | ", () => GetSetting(ChatBoxTextSetting.Animate)); + CreateSetting(ChatBoxTextSetting.MaxLength, "Max Length", "The maximum length to show at one time when animating", 16, () => GetSetting(ChatBoxTextSetting.Animate)); - return tickerText; + CreateState(ChatBoxTextState.Default, "Default", "{text}"); + CreateVariable(ChatBoxTextVariable.Text, "Text", "{text}"); } protected override void OnModuleStart() @@ -43,16 +36,25 @@ protected override void OnModuleStart() protected override void OnModuleUpdate() { index += GetSetting(ChatBoxTextSetting.ScrollSpeed); - } - protected override void CreateAttributes() - { - CreateSetting(ChatBoxTextSetting.ChatBoxText, "ChatBox Text", "What text should be displayed in the ChatBox?", string.Empty); - CreateSetting(ChatBoxTextSetting.Animate, "Animate", "Should the text animate like a ticker tape?", false); - CreateSetting(ChatBoxTextSetting.ScrollSpeed, "Scroll Speed", "How fast should the text scroll? Measured in characters per update.", 1, () => GetSetting(ChatBoxTextSetting.Animate)); - CreateSetting(ChatBoxTextSetting.Splitter, "Splitter", "The splitter that goes between loops of the text", " | ", () => GetSetting(ChatBoxTextSetting.Animate)); - CreateSetting(ChatBoxTextSetting.MaxLength, "Max Length", "The maximum length to show at one time when animating", 16, () => GetSetting(ChatBoxTextSetting.Animate)); - base.CreateAttributes(); + var text = GetSetting(ChatBoxTextSetting.ChatBoxText); + + if (!GetSetting(ChatBoxTextSetting.Animate)) + { + SetVariableValue(ChatBoxTextVariable.Text, text); + return; + } + + var splitter = GetSetting(ChatBoxTextSetting.Splitter); + + var tickerText = $"{text}{splitter}{text}"; + var maxLength = Math.Min(GetSetting(ChatBoxTextSetting.MaxLength), text.Length); + + if (index > text.Length + splitter.Length - 1) index = 0; + + tickerText = tickerText[index..(maxLength + index)]; + + SetVariableValue(ChatBoxTextVariable.Text, tickerText); } private enum ChatBoxTextSetting @@ -63,4 +65,14 @@ private enum ChatBoxTextSetting Splitter, MaxLength } + + private enum ChatBoxTextState + { + Default + } + + private enum ChatBoxTextVariable + { + Text + } } diff --git a/VRCOSC.Modules/Clock/ClockModule.cs b/VRCOSC.Modules/Clock/ClockModule.cs index b8402bd3..675b3e90 100644 --- a/VRCOSC.Modules/Clock/ClockModule.cs +++ b/VRCOSC.Modules/Clock/ClockModule.cs @@ -2,11 +2,12 @@ // See the LICENSE file in the repository root for full license text. using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; using VRCOSC.Game.OSC.VRChat; namespace VRCOSC.Modules.Clock; -public sealed partial class ClockModule : ChatBoxModule +public sealed class ClockModule : ChatBoxModule { public override string Title => "Clock"; public override string Description => "Sends your local time as hours, minutes, and seconds"; @@ -15,9 +16,6 @@ public sealed partial class ClockModule : ChatBoxModule public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => GetSetting(ClockSetting.SmoothSecond) ? VRChatOscConstants.UPDATE_TIME_SPAN : TimeSpan.FromSeconds(1); - protected override string DefaultChatBoxFormat => "Local Time %h%:%m%%period%"; - protected override IEnumerable ChatBoxFormatValues => new[] { "%h%", "%m%", "%s%", "%period%" }; - private DateTime time; protected override void CreateAttributes() @@ -28,22 +26,16 @@ protected override void CreateAttributes() CreateSetting(ClockSetting.Mode, "Mode", "If the clock should be in 12 hour or 24 hour", ClockMode.Twelve); CreateSetting(ClockSetting.Timezone, "Timezone", "The timezone the clock should follow", ClockTimeZone.Local); - base.CreateAttributes(); - CreateParameter(ClockParameter.Hours, ParameterMode.Write, "VRCOSC/Clock/Hours", "Hours", "The current hour normalised"); CreateParameter(ClockParameter.Minutes, ParameterMode.Write, "VRCOSC/Clock/Minutes", "Minutes", "The current minute normalised"); CreateParameter(ClockParameter.Seconds, ParameterMode.Write, "VRCOSC/Clock/Seconds", "Seconds", "The current second normalised"); - } - protected override string? GetChatBoxText() - { - var chatBoxTime = timezoneToTime(GetSetting(ClockSetting.Timezone)); - var textHour = GetSetting(ClockSetting.Mode) == ClockMode.Twelve ? (chatBoxTime.Hour % 12).ToString("00") : chatBoxTime.Hour.ToString("00"); - return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace("%h%", textHour) - .Replace("%m%", chatBoxTime.Minute.ToString("00")) - .Replace("%s%", chatBoxTime.Second.ToString("00")) - .Replace("%period%", chatBoxTime.Hour >= 12 ? "pm" : "am"); + CreateState(ClockState.Default, "Default", "Local Time {h}:{m}{period}"); + + CreateVariable(ClockVariable.Hours, "Hours", "{h}"); + CreateVariable(ClockVariable.Minutes, "Minutes", "{m}"); + CreateVariable(ClockVariable.Seconds, "Seconds", "{s}"); + CreateVariable(ClockVariable.Period, "AM/PM", "{period}"); } protected override void OnModuleUpdate() @@ -62,6 +54,16 @@ protected override void OnModuleUpdate() SendParameter(ClockParameter.Hours, hourNormalised); SendParameter(ClockParameter.Minutes, minuteNormalised); SendParameter(ClockParameter.Seconds, secondNormalised); + + var hourText = GetSetting(ClockSetting.Mode) == ClockMode.Twelve ? (time.Hour % 12).ToString("00") : time.Hour.ToString("00"); + var minuteText = time.Minute.ToString("00"); + var secondText = time.Second.ToString("00"); + var periodText = time.Hour >= 12 ? "pm" : "am"; + + SetVariableValue(ClockVariable.Hours, hourText); + SetVariableValue(ClockVariable.Minutes, minuteText); + SetVariableValue(ClockVariable.Seconds, secondText); + SetVariableValue(ClockVariable.Period, periodText); } private static float getSmoothedSeconds(DateTime time) => time.Second + time.Millisecond / 1000f; @@ -99,6 +101,19 @@ private enum ClockSetting Mode } + private enum ClockState + { + Default + } + + private enum ClockVariable + { + Hours, + Minutes, + Seconds, + Period + } + private enum ClockMode { Twelve, diff --git a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs index 6ed485d8..a873836c 100644 --- a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs @@ -2,11 +2,12 @@ // See the LICENSE file in the repository root for full license text. using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; using VRCOSC.Game.Providers.Hardware; namespace VRCOSC.Modules.HardwareStats; -public sealed partial class HardwareStatsModule : ChatBoxModule +public sealed class HardwareStatsModule : ChatBoxModule { public override string Title => "Hardware Stats"; public override string Description => "Sends hardware stats and displays them in the ChatBox"; @@ -14,11 +15,6 @@ public sealed partial class HardwareStatsModule : ChatBoxModule public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(0.5); - protected override string DefaultChatBoxFormat => @"CPU: $cpuusage$% | GPU: $gpuusage$% RAM: $ramused$GB/$ramtotal$GB"; - - protected override IEnumerable ChatBoxFormatValues => new[] - { @"$cpuusage$ (%)", @"$gpuusage$ (%)", @"$ramusage$ (%)", @"$cputemp$ (C)", @"$gputemp$ (C)", @"$ramtotal$ (GB)", @"$ramused$ (GB)", @"$ramavailable$ (GB)", @"$vramused$ (GB)", @"$vramfree$ (GB)", @"$vramtotal$ (GB)" }; - private HardwareStatsProvider? hardwareStatsProvider; protected override void CreateAttributes() @@ -26,8 +22,6 @@ protected override void CreateAttributes() CreateSetting(HardwareStatsSetting.SelectedCPU, "Selected CPU", "If you have multiple CPUs, enter the (0th based) index of the one you want to track", 0); CreateSetting(HardwareStatsSetting.SelectedGPU, "Selected GPU", "If you have multiple GPUs, enter the (0th based) index of the one you want to track", 0); - base.CreateAttributes(); - CreateParameter(HardwareStatsParameter.CpuUsage, ParameterMode.Write, "VRCOSC/Hardware/CPUUsage", "CPU Usage", "The CPU usage normalised"); CreateParameter(HardwareStatsParameter.GpuUsage, ParameterMode.Write, "VRCOSC/Hardware/GPUUsage", "GPU Usage", "The GPU usage normalised"); CreateParameter(HardwareStatsParameter.RamUsage, ParameterMode.Write, "VRCOSC/Hardware/RAMUsage", "RAM Usage", "The RAM usage normalised"); @@ -36,37 +30,24 @@ protected override void CreateAttributes() CreateParameter(HardwareStatsParameter.RamTotal, ParameterMode.Write, "VRCOSC/Hardware/RAMTotal", "RAM Total", "The total amount of RAM in GB"); CreateParameter(HardwareStatsParameter.RamUsed, ParameterMode.Write, "VRCOSC/Hardware/RAMUsed", "RAM Used", "The used RAM in GB"); CreateParameter(HardwareStatsParameter.RamAvailable, ParameterMode.Write, "VRCOSC/Hardware/RAMAvailable", "RAM Available", "The available RAM in GB"); - CreateParameter(HardwareStatsParameter.VRamFree, ParameterMode.Write, "VRCOSC/Hardware/VRamFree", "VRAM Free", "The amount of free VRAM in GB"); - CreateParameter(HardwareStatsParameter.VRamUsed, ParameterMode.Write, "VRCOSC/Hardware/VRamUsed", "VRAM Used", "The amount of used VRAM in GB"); - CreateParameter(HardwareStatsParameter.VRamTotal, ParameterMode.Write, "VRCOSC/Hardware/VRamTotal", "VRAM Total", "The amount of total VRAM in GB"); - } - - protected override string? GetChatBoxText() - { - if (hardwareStatsProvider is null || !hardwareStatsProvider.CanAcceptQueries) return null; - - try - { - var cpu = hardwareStatsProvider.Cpus[GetSetting(HardwareStatsSetting.SelectedCPU)]; - var gpu = hardwareStatsProvider.Gpus[GetSetting(HardwareStatsSetting.SelectedGPU)]; - var ram = hardwareStatsProvider.Ram; - - return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace(@"$cpuusage$", cpu.Usage.ToString("0.00")) - .Replace(@"$gpuusage$", gpu.Usage.ToString("0.00")) - .Replace(@"$ramusage$", ram.Usage.ToString("0.00")) - .Replace(@"$cputemp$", cpu.Temperature.ToString()) - .Replace(@"$gputemp$", gpu.Temperature.ToString()) - .Replace(@"$ramtotal$", ram.Total.ToString("0.0")) - .Replace(@"$ramused$", ram.Used.ToString("0.0")) - .Replace(@"$ramavailable$", ram.Available.ToString("0.0")) - .Replace(@"$vramfree$", (gpu.MemoryFree / 1000f).ToString("0.0")) - .Replace(@"$vramused$", (gpu.MemoryUsed / 1000f).ToString("0.0")) - .Replace(@"$vramtotal$", (gpu.MemoryTotal / 1000f).ToString("0.0")); - } - catch { } - - return null; + CreateParameter(HardwareStatsParameter.VRamFree, ParameterMode.Write, "VRCOSC/Hardware/VRamFree", @"VRAM Free", @"The amount of free VRAM in GB"); + CreateParameter(HardwareStatsParameter.VRamUsed, ParameterMode.Write, "VRCOSC/Hardware/VRamUsed", @"VRAM Used", @"The amount of used VRAM in GB"); + CreateParameter(HardwareStatsParameter.VRamTotal, ParameterMode.Write, "VRCOSC/Hardware/VRamTotal", @"VRAM Total", @"The amount of total VRAM in GB"); + + CreateState(HardwareStatsState.Available, "Available", @"CPU: {cpuusage}% | GPU: {gpuusage}% RAM: {ramused}GB/{ramtotal}GB"); + CreateState(HardwareStatsState.Unavailable, "Unavailable", string.Empty); + + CreateVariable(HardwareStatsParameter.CpuUsage, @"CPU Usage (%)", @"{cpuusage}"); + CreateVariable(HardwareStatsParameter.GpuUsage, @"GPU Usage (%)", @"{gpuusage}"); + CreateVariable(HardwareStatsParameter.RamUsage, @"RAM Usage (%)", @"{ramusage}"); + CreateVariable(HardwareStatsParameter.CpuTemp, @"CPU Temp (C)", @"{cputemp}"); + CreateVariable(HardwareStatsParameter.GpuTemp, @"GPU Temp (C)", @"{gputemp}"); + CreateVariable(HardwareStatsParameter.RamTotal, @"RAM Total (GB)", @"{ramtotal}"); + CreateVariable(HardwareStatsParameter.RamUsed, @"RAM Used (GB)", @"{ramused}"); + CreateVariable(HardwareStatsParameter.RamAvailable, @"RAM Available (GB)", @"{ramavailable}"); + CreateVariable(HardwareStatsParameter.VRamUsed, @"VRAM Used (GB)", @"{vramused}"); + CreateVariable(HardwareStatsParameter.VRamFree, @"VRAM Free (GB)", @"{vramfree}"); + CreateVariable(HardwareStatsParameter.VRamTotal, @"VRAM Total (GB)", @"{vramtotal}"); } protected override void OnModuleStart() @@ -98,6 +79,18 @@ protected override void OnModuleUpdate() SendParameter(HardwareStatsParameter.VRamFree, gpu.MemoryFree / 1000f); SendParameter(HardwareStatsParameter.VRamUsed, gpu.MemoryUsed / 1000f); SendParameter(HardwareStatsParameter.VRamTotal, gpu.MemoryTotal / 1000f); + + SetVariableValue(HardwareStatsParameter.CpuUsage, cpu.Usage.ToString("0.00")); + SetVariableValue(HardwareStatsParameter.GpuUsage, gpu.Usage.ToString("0.00")); + SetVariableValue(HardwareStatsParameter.RamUsage, ram.Usage.ToString("0.00")); + SetVariableValue(HardwareStatsParameter.CpuTemp, cpu.Temperature.ToString()); + SetVariableValue(HardwareStatsParameter.GpuTemp, gpu.Temperature.ToString()); + SetVariableValue(HardwareStatsParameter.RamTotal, ram.Total.ToString("0.0")); + SetVariableValue(HardwareStatsParameter.RamUsed, ram.Used.ToString("0.0")); + SetVariableValue(HardwareStatsParameter.RamAvailable, ram.Available.ToString("0.0")); + SetVariableValue(HardwareStatsParameter.VRamFree, (gpu.MemoryFree / 1000f).ToString("0.0")); + SetVariableValue(HardwareStatsParameter.VRamUsed, (gpu.MemoryUsed / 1000f).ToString("0.0")); + SetVariableValue(HardwareStatsParameter.VRamTotal, (gpu.MemoryTotal / 1000f).ToString("0.0")); } catch { } } @@ -127,4 +120,10 @@ private enum HardwareStatsParameter VRamUsed, VRamTotal } + + private enum HardwareStatsState + { + Available, + Unavailable + } } diff --git a/VRCOSC.Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Modules/Heartrate/HeartRateModule.cs index fa4e88f6..ab58254f 100644 --- a/VRCOSC.Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HeartRateModule.cs @@ -2,10 +2,11 @@ // See the LICENSE file in the repository root for full license text. using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; namespace VRCOSC.Modules.Heartrate; -public abstract partial class HeartRateModule : ChatBoxModule +public abstract class HeartRateModule : ChatBoxModule { private static readonly TimeSpan heartrate_timeout = TimeSpan.FromSeconds(10); @@ -13,11 +14,6 @@ public abstract partial class HeartRateModule : ChatBoxModule public override string Prefab => "VRCOSC-Heartrate"; public override ModuleType Type => ModuleType.Health; protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(2); - protected override int ChatBoxPriority => 1; - - protected override bool DefaultChatBoxDisplay => false; - protected override string DefaultChatBoxFormat => "Heartrate %hr% bpm"; - protected override IEnumerable ChatBoxFormatValues => new[] { "%hr%" }; protected HeartRateProvider? HeartRateProvider; private int lastHeartrate; @@ -30,15 +26,16 @@ public abstract partial class HeartRateModule : ChatBoxModule protected override void CreateAttributes() { - base.CreateAttributes(); CreateParameter(HeartrateParameter.Enabled, ParameterMode.Write, "VRCOSC/Heartrate/Enabled", "Enabled", "Whether this module is attempting to emit values"); CreateParameter(HeartrateParameter.Normalised, ParameterMode.Write, "VRCOSC/Heartrate/Normalised", "Normalised", "The heartrate value normalised to 240bpm"); CreateParameter(HeartrateParameter.Units, ParameterMode.Write, "VRCOSC/Heartrate/Units", "Units", "The units digit 0-9 mapped to a float"); CreateParameter(HeartrateParameter.Tens, ParameterMode.Write, "VRCOSC/Heartrate/Tens", "Tens", "The tens digit 0-9 mapped to a float"); CreateParameter(HeartrateParameter.Hundreds, ParameterMode.Write, "VRCOSC/Heartrate/Hundreds", "Hundreds", "The hundreds digit 0-9 mapped to a float"); - } - protected override string GetChatBoxText() => GetSetting(ChatBoxSetting.ChatBoxFormat).Replace("%hr%", lastHeartrate.ToString()); + CreateState(HeartrateState.Default, @"Default", @"Heartrate {hr} bpm"); + + CreateVariable(HeartrateVariable.Heartrate, @"Heartrate", @"{hr}"); + } protected override void OnModuleStart() { @@ -60,6 +57,7 @@ private void attemptConnection() HeartRateProvider = CreateHeartRateProvider(); HeartRateProvider.OnHeartRateUpdate += HandleHeartRateUpdate; HeartRateProvider.OnConnected += () => connectionCount = 0; + HeartRateProvider.OnDisconnected += () => { Task.Run(async () => @@ -86,6 +84,8 @@ protected override void OnModuleStop() protected override void OnModuleUpdate() { if (!isReceiving) SendParameter(HeartrateParameter.Enabled, false); + + SetVariableValue(HeartrateVariable.Heartrate, lastHeartrate.ToString()); } protected virtual void HandleHeartRateUpdate(int heartrate) @@ -116,4 +116,14 @@ protected enum HeartrateParameter Tens, Hundreds } + + private enum HeartrateState + { + Default + } + + private enum HeartrateVariable + { + Heartrate + } } diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index aeb0479f..d6457b02 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -5,22 +5,19 @@ using Windows.Media; using osu.Framework.Bindables; using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; using VRCOSC.Game.Providers.Media; namespace VRCOSC.Modules.Media; -public sealed partial class MediaModule : ChatBoxModule +public class MediaModule : ChatBoxModule { public override string Title => "Media"; public override string Description => "Integration with Windows Media"; public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Media"; - protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(2); + protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(1); public override ModuleType Type => ModuleType.Integrations; - protected override int ChatBoxPriority => 2; - - protected override string DefaultChatBoxFormat => @"[%curtime%/%duration%] Now Playing: %artist% - %title%"; - protected override IEnumerable ChatBoxFormatValues => new[] { @"%title%", @"%artist%", @"%curtime%", @"%duration%", @"%volume%" }; private readonly WindowsMediaProvider mediaProvider = new(); private readonly Bindable currentlySeeking = new(); @@ -33,12 +30,8 @@ public MediaModule() 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]", () => 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", "Play/Pause", @"True for playing. False for paused"); CreateParameter(MediaParameter.Volume, ParameterMode.ReadWrite, @"VRCOSC/Media/Volume", "Volume", @"The volume of the process that is controlling the media"); CreateParameter(MediaParameter.Repeat, ParameterMode.ReadWrite, @"VRCOSC/Media/Repeat", "Repeat", @"0 for disabled. 1 for single. 2 for list"); @@ -47,30 +40,15 @@ protected override void CreateAttributes() CreateParameter(MediaParameter.Previous, ParameterMode.Read, @"VRCOSC/Media/Previous", "Previous", @"Becoming true causes the previous track to play"); CreateParameter(MediaParameter.Seeking, ParameterMode.Read, @"VRCOSC/Media/Seeking", "Seeking", "Whether the user is currently seeking"); CreateParameter(MediaParameter.Position, ParameterMode.ReadWrite, @"VRCOSC/Media/Position", "Position", "The position of the song as a percentage"); - } - protected override string? GetChatBoxText() - { - if (mediaProvider.Controller is null) return null; + CreateState(MediaState.Playing, "Playing", @"[{time}/{duration}] Now Playing: {artist} - {title}"); + CreateState(MediaState.Paused, "Paused", @"[Paused]"); - if (!mediaProvider.State.IsPlaying) - { - if (GetSetting(MediaSetting.PausedBehaviour) == MediaPausedBehaviour.Empty) return null; - - return GetSetting(MediaSetting.PausedText) - .Replace(@"%title%", mediaProvider.State.Title) - .Replace(@"%artist%", mediaProvider.State.Artist) - .Replace(@"%volume%", (mediaProvider.State.Volume * 100).ToString("##0")); - } - - var formattedText = GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace(@"%title%", mediaProvider.State.Title) - .Replace(@"%artist%", mediaProvider.State.Artist) - .Replace(@"%curtime%", mediaProvider.State.Position?.Position.ToString(@"mm\:ss")) - .Replace(@"%duration%", mediaProvider.State.Position?.EndTime.ToString(@"mm\:ss")) - .Replace(@"%volume%", (mediaProvider.State.Volume * 100).ToString("##0")); - - return formattedText; + CreateVariable(MediaVariable.Title, @"Title", @"{title}"); + CreateVariable(MediaVariable.Artist, @"Artist", @"{artist}"); + CreateVariable(MediaVariable.Time, @"Time", @"{time}"); + CreateVariable(MediaVariable.Duration, @"Duration", @"{duration}"); + CreateVariable(MediaVariable.Volume, @"Volume", @"{volume}"); } protected override async void OnModuleStart() @@ -114,6 +92,12 @@ protected override void OnAvatarChange() protected override void OnModuleUpdate() { if (mediaProvider.Controller is not null) sendUpdatableParameters(); + + SetVariableValue(MediaVariable.Title, mediaProvider.State.Title); + SetVariableValue(MediaVariable.Artist, mediaProvider.State.Artist); + SetVariableValue(MediaVariable.Title, mediaProvider.State.Position?.Position.ToString(@"mm\:ss")); + SetVariableValue(MediaVariable.Duration, mediaProvider.State.Position?.EndTime.ToString(@"mm\:ss")); + SetVariableValue(MediaVariable.Volume, (mediaProvider.State.Volume * 100).ToString("##0")); } private void onPlaybackStateUpdate() @@ -209,10 +193,19 @@ private enum MediaSetting StartList } - private enum MediaPausedBehaviour + private enum MediaState + { + Playing, + Paused + } + + private enum MediaVariable { - Empty, - Display + Title, + Artist, + Time, + Duration, + Volume } private enum MediaParameter diff --git a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs index dbf50de0..41e44fa2 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs @@ -1,29 +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.Globalization; using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; using VRCOSC.Game.OpenVR; namespace VRCOSC.Modules.OpenVR; -public partial class OpenVRStatisticsModule : ChatBoxModule +public class OpenVRStatisticsModule : ChatBoxModule { 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 TimeSpan DeltaUpdate => TimeSpan.FromSeconds(5); - protected override int ChatBoxPriority => 1; - - protected override bool DefaultChatBoxDisplay => false; - protected override IEnumerable ChatBoxFormatValues => new[] { "$fps$", @"$hmdbattery$", @"$leftcontrollerbattery$", @"$rightcontrollerbattery$" }; - protected override string DefaultChatBoxFormat => @"FPS: $fps$ | HMD: $hmdbattery$ | LC: $leftcontrollerbattery$ | RC: $rightcontrollerbattery$"; protected override void CreateAttributes() { - base.CreateAttributes(); - CreateParameter(OpenVrParameter.FPS, ParameterMode.Write, "VRCOSC/OpenVR/FPS", "FPS", "The current FPS normalised to 240 FPS"); CreateParameter(OpenVrParameter.HMD_Connected, ParameterMode.Write, "VRCOSC/OpenVR/HMD/Connected", "HMD Connected", "Whether your HMD is connected"); @@ -43,27 +36,36 @@ protected override void CreateAttributes() CreateParameter(OpenVrParameter.Tracker1_Battery + i, ParameterMode.Write, $"VRCOSC/OpenVR/Trackers/{i + 1}/Battery", $"Tracker {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", $"Tracker {i + 1} Charging", $"Whether tracker {i + 1} is currently charging"); } - } - protected override string? GetChatBoxText() - { - if (!OVRClient.HasInitialised) return null; + CreateState(OpenVrState.Default, "Default", @"FPS: {fps} | HMD: {hmdbattery} | LC: {leftcontrollerbattery} | RC: {rightcontrollerbattery$}"); - return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace("$fps$", OVRClient.System.FPS.ToString("00", CultureInfo.InvariantCulture)) - .Replace(@"$hmdbattery$", ((int)(OVRClient.HMD.BatteryPercentage * 100)).ToString(CultureInfo.InvariantCulture)) - .Replace(@"$leftcontrollerbattery$", ((int)(OVRClient.LeftController.BatteryPercentage * 100)).ToString(CultureInfo.InvariantCulture)) - .Replace(@"$rightcontrollerbattery$", ((int)(OVRClient.RightController.BatteryPercentage * 100)).ToString(CultureInfo.InvariantCulture)); + CreateVariable(OpenVrVariable.FPS, @"FPS", @"{fps}"); + CreateVariable(OpenVrVariable.HMDBattery, @"HMD Battery", @"{hmdbattery}"); + CreateVariable(OpenVrVariable.LeftControllerBattery, @"Left Controller Battery", @"{leftcontrollerbattery}"); + CreateVariable(OpenVrVariable.RightControllerBattery, @"Right Controller Battery", @"{rightcontrollerbattery}"); } protected override void OnModuleUpdate() { - if (!OVRClient.HasInitialised) return; - - SendParameter(OpenVrParameter.FPS, OVRClient.System.FPS / 240.0f); - handleHmd(); - handleControllers(); - handleTrackers(); + if (OVRClient.HasInitialised) + { + SendParameter(OpenVrParameter.FPS, OVRClient.System.FPS / 240.0f); + handleHmd(); + handleControllers(); + handleTrackers(); + + SetVariableValue(OpenVrVariable.FPS, OVRClient.System.FPS.ToString("00")); + SetVariableValue(OpenVrVariable.HMDBattery, ((int)(OVRClient.HMD.BatteryPercentage * 100)).ToString()); + SetVariableValue(OpenVrVariable.LeftControllerBattery, ((int)(OVRClient.LeftController.BatteryPercentage * 100)).ToString()); + SetVariableValue(OpenVrVariable.RightControllerBattery, ((int)(OVRClient.RightController.BatteryPercentage * 100)).ToString()); + } + else + { + SetVariableValue(OpenVrVariable.FPS, "0"); + SetVariableValue(OpenVrVariable.HMDBattery, "0"); + SetVariableValue(OpenVrVariable.LeftControllerBattery, "0"); + SetVariableValue(OpenVrVariable.RightControllerBattery, "0"); + } } private void handleHmd() @@ -151,4 +153,17 @@ private enum OpenVrParameter Tracker7_Charging, Tracker8_Charging } + + private enum OpenVrState + { + Default + } + + private enum OpenVrVariable + { + FPS, + HMDBattery, + LeftControllerBattery, + RightControllerBattery + } } diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index c027cfba..637b8ee8 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -3,20 +3,17 @@ using VRCOSC.Game; using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; namespace VRCOSC.Modules.Weather; -public partial class WeatherModule : ChatBoxModule +public class WeatherModule : ChatBoxModule { public override string Title => "Weather"; public override string Description => "Retrieves weather information for a specific area"; public override string Author => "VolcanicArts"; public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => TimeSpan.FromMinutes(10); - protected override int ChatBoxPriority => 1; - - protected override IEnumerable ChatBoxFormatValues => new[] { @"%tempc%", @"%tempf%", @"%humidity%" }; - protected override string DefaultChatBoxFormat => @"Local Weather %tempc%C"; private WeatherProvider? weatherProvider; private Weather? currentWeather; @@ -25,9 +22,13 @@ protected override void CreateAttributes() { CreateSetting(WeatherSetting.Postcode, "Location", "The postcode/zip code or city name to retrieve weather data for", string.Empty); - base.CreateAttributes(); - CreateParameter(WeatherParameter.Code, ParameterMode.Write, "VRCOSC/Weather/Code", "Weather Code", "The current weather's code"); + + CreateState(WeatherState.Default, @"Default", @"Local Weather {tempc}C - {tempf}F"); + + CreateVariable(WeatherVariable.TempC, @"Temp C", @"{tempc}"); + CreateVariable(WeatherVariable.TempF, @"Temp F", @"{tempf}"); + CreateVariable(WeatherVariable.Humidity, @"Humidity", @"{humidity}"); } protected override void OnModuleStart() @@ -49,7 +50,7 @@ protected override void OnModuleUpdate() Task.Run(async () => { currentWeather = await weatherProvider.RetrieveFor(GetSetting(WeatherSetting.Postcode)); - sendParameters(); + updateParameters(); }); } @@ -60,24 +61,18 @@ protected override void OnModuleStop() protected override void OnAvatarChange() { - sendParameters(); - } - - protected override string? GetChatBoxText() - { - if (currentWeather is null) return null; - - return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace(@"%tempc%", currentWeather.TempC.ToString("0.0")) - .Replace(@"%tempf%", currentWeather.TempF.ToString("0.0")) - .Replace("%humidity%", currentWeather.Humidity.ToString()); + updateParameters(); } - private void sendParameters() + private void updateParameters() { if (currentWeather is null) return; SendParameter(WeatherParameter.Code, convertedWeatherCode); + + SetVariableValue(WeatherVariable.TempC, currentWeather.TempC.ToString("0.0")); + SetVariableValue(WeatherVariable.TempF, currentWeather.TempF.ToString("0.0")); + SetVariableValue(WeatherVariable.Humidity, currentWeather.Humidity.ToString()); } private int convertedWeatherCode => currentWeather!.Condition.Code switch @@ -142,4 +137,16 @@ private enum WeatherParameter { Code } + + private enum WeatherState + { + Default + } + + private enum WeatherVariable + { + TempC, + TempF, + Humidity + } } From 68775c8d114369eaeb620a6caee24533698e6ead Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 11:04:44 +0100 Subject: [PATCH 29/59] Add clip events and prepare clips for evaluation calculation --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 120 +++++++++++++++++- VRCOSC.Game/ChatBox/Clips/Clip.cs | 98 +++++++++++++- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 4 +- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 3 +- .../ClipVariable.cs} | 5 +- .../ChatBox/Clips/CompoundClipState.cs | 11 ++ .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 2 +- VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs | 16 ++- .../Providers/Media/WindowsMediaProvider.cs | 3 + VRCOSC.Modules/AFK/AFKModule.cs | 2 + VRCOSC.Modules/Media/MediaModule.cs | 22 +++- 11 files changed, 262 insertions(+), 24 deletions(-) rename VRCOSC.Game/ChatBox/{ChatBoxVariable.cs => Clips/ClipVariable.cs} (78%) create mode 100644 VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index e05f322a..a58a063e 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -13,7 +13,15 @@ public class ChatBoxManager { public readonly BindableList Clips = new(); - public readonly Dictionary Variables = new(); + public readonly Dictionary> Variables = new(); + public readonly Dictionary> States = new(); + public readonly Dictionary> Events = new(); + + public readonly Dictionary ModuleStates = new(); + + // TODO - module events will become true for 1 update check and then reset to false + // Each clip evaluation that checks if an event has occured will have to locally store a DateTimeOffset + public readonly Dictionary> ModuleEvents = new(); public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); @@ -25,7 +33,7 @@ public ChatBoxManager() { Clip clip; - Clips.Add(clip = new Clip + Clips.Add(clip = new Clip(this) { Priority = { Value = i }, }); @@ -33,6 +41,19 @@ public ChatBoxManager() } } + public Clip CreateClip() => new(this); + + public void Initialise() + { + } + + public void Update() + { + // evaluate clips + + // reset module event triggers + } + public bool IncreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value + 1); public bool DecreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value - 1); @@ -45,19 +66,104 @@ public bool SetPriority(Clip clip, int priority) return true; } - public void RegisterVariable(string lookup, string name, string format) + public void RegisterVariable(string module, string lookup, string name, string format) { - Variables.Add(lookup, new ChatBoxVariable + var variable = new ClipVariable { + Module = module, Lookup = lookup, Name = name, Format = format - }); + }; + + if (Variables.TryGetValue(module, out var innerDict)) + { + innerDict.Add(lookup, variable); + } + else + { + Variables.Add(module, new Dictionary()); + Variables[module].Add(lookup, variable); + } + } + + public void SetVariable(string module, string lookup, string? value) + { + Variables[module][lookup].Value = value; + } + + public void RegisterState(string module, string lookup, string name, string defaultFormat) + { + var state = new ClipState + { + Lookup = lookup, + Name = name, + Format = { Value = defaultFormat } + }; + + if (States.TryGetValue(module, out var innerDict)) + { + innerDict.Add(lookup, state); + } + else + { + States.Add(module, new Dictionary()); + States[module].Add(lookup, state); + } + + if (!ModuleStates.TryAdd(module, lookup)) + { + ModuleStates[module] = lookup; + } + } + + public void ChangeStateTo(string module, string lookup) + { + ModuleStates[module] = lookup; + } + + public void RegisterEvent(string module, string lookup, string name, string defaultFormat, int defaultLength) + { + var clipEvent = new ClipEvent + { + Lookup = lookup, + Name = name, + Format = { Value = defaultFormat }, + Length = { Value = defaultLength } + }; + + if (Events.TryGetValue(module, out var innerDict)) + { + innerDict.Add(lookup, clipEvent); + } + else + { + Events.Add(module, new Dictionary()); + Events[module].Add(lookup, clipEvent); + } + + if (ModuleEvents.TryGetValue(module, out var innerDict2)) + { + innerDict2[lookup] = false; + } + else + { + ModuleEvents.Add(module, new Dictionary()); + + if (ModuleEvents[module].ContainsKey(lookup)) + { + ModuleEvents[module][lookup] = false; + } + else + { + ModuleEvents[module].Add(lookup, false); + } + } } - public void SetVariable(string lookup, string? value) + public void TriggerEvent(string module, string lookup) { - Variables[lookup].Value = value; + ModuleEvents[module][lookup] = true; } public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index ba0aabcc..f08a4a3f 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -1,7 +1,10 @@ // 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.Collections.Specialized; +using System.Linq; using osu.Framework.Bindables; namespace VRCOSC.Game.ChatBox.Clips; @@ -15,25 +18,106 @@ public class Clip public readonly Bindable Name = new("New Clip"); public readonly BindableNumber Priority = new(); public readonly BindableList AssociatedModules = new(); - public readonly BindableList AvailableVariables = new(); - public readonly Dictionary States = new(); - public readonly Dictionary Events = new(); + public readonly BindableList AvailableVariables = new(); + public readonly BindableDictionary> States = new(); + public readonly BindableDictionary> Events = new(); public readonly Bindable Start = new(); public readonly Bindable End = new(30); public int Length => End.Value - Start.Value; - public Clip() + public readonly Dictionary> ModuleEvents = new(); + + private readonly ChatBoxManager chatBoxManager; + + public Clip(ChatBoxManager chatBoxManager) + { + this.chatBoxManager = chatBoxManager; + AssociatedModules.BindCollectionChanged((_, e) => onAssociatedModulesChanged(e), true); + } + + public void Update() { - AssociatedModules.BindCollectionChanged((_, _) => calculateAvailableVariables(), true); + // check for events } - private void calculateAvailableVariables() + public bool Evalulate() + { + if (!Enabled.Value) return false; + + // check states and compound states for what state we're currently in and check to see if that state is enabled + // check if there are any ongoing events + + return true; + } + + private void onAssociatedModulesChanged(NotifyCollectionChangedEventArgs e) + { + populateAvailableVariables(); + populateStates(e); + populateEvents(e); + } + + private void populateAvailableVariables() { AvailableVariables.Clear(); foreach (var module in AssociatedModules) { - //AvailableVariables.AddRange(module.Variables); + AvailableVariables.AddRange(chatBoxManager.Variables[module].Values.ToList()); + } + } + + private void populateStates(NotifyCollectionChangedEventArgs e) + { + if (e.NewItems is not null) + { + foreach (string newModule in e.NewItems) + { + var states = chatBoxManager.States[newModule]; + + States.Add(newModule, new Dictionary()); + + foreach (var (key, value) in states) + { + States[newModule][key] = value; + } + } + } + + if (e.OldItems is not null) + { + foreach (string oldModule in e.OldItems) + { + States.Remove(oldModule); + } + } + + // compound state calculation + } + + private void populateEvents(NotifyCollectionChangedEventArgs e) + { + if (e.NewItems is not null) + { + foreach (string newModule in e.NewItems) + { + var events = chatBoxManager.Events[newModule]; + + Events.Add(newModule, new Dictionary()); + + foreach (var (key, value) in events) + { + Events[newModule][key] = value; + } + } + } + + if (e.OldItems is not null) + { + foreach (string oldModule in e.OldItems) + { + Events.Remove(oldModule); + } } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index 090ce491..ad0692c3 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -1,11 +1,11 @@ // 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; namespace VRCOSC.Game.ChatBox.Clips; public class ClipEvent : ClipState { - public TimeSpan Time = TimeSpan.Zero; + public Bindable Length = new(); } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index c3b1a94c..5a325dce 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -7,7 +7,8 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { + public required string Lookup { get; init; } public required string Name { get; init; } - public string Format { get; init; } = string.Empty; + public Bindable Format = new(); public BindableBool Enabled = new(); } diff --git a/VRCOSC.Game/ChatBox/ChatBoxVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs similarity index 78% rename from VRCOSC.Game/ChatBox/ChatBoxVariable.cs rename to VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index 37b4e4d3..9fd87db7 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -1,13 +1,14 @@ // 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.ChatBox; +namespace VRCOSC.Game.ChatBox.Clips; /// /// Used by modules to denote a provided variable /// -public class ChatBoxVariable +public class ClipVariable { + public required string Module { get; init; } public required string Lookup { get; init; } public required string Name { get; init; } public required string Format { get; init; } diff --git a/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs b/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs new file mode 100644 index 00000000..1641c1f8 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs @@ -0,0 +1,11 @@ +// 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; + +namespace VRCOSC.Game.ChatBox.Clips; + +public class CompoundClipState +{ + private List states = new(); +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index 83cc7675..c0f28bc2 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -38,7 +38,7 @@ private void load() private void createClip() { - var clip = new Game.ChatBox.Clips.Clip(); + var clip = chatBoxManager.CreateClip(); var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false); diff --git a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs index 1e544bec..329e63af 100644 --- a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs +++ b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs @@ -9,21 +9,31 @@ public abstract class ChatBoxModule : Module { protected void CreateVariable(Enum lookup, string name, string format) { - ChatBoxManager.RegisterVariable(SerialisedName + "-" + lookup.ToLookup(), name, format); + ChatBoxManager.RegisterVariable(SerialisedName, lookup.ToLookup(), name, format); } protected void SetVariableValue(Enum lookup, string? value) { - ChatBoxManager.SetVariable(SerialisedName + "-" + lookup.ToLookup(), value); + ChatBoxManager.SetVariable(SerialisedName, lookup.ToLookup(), value); } protected void CreateState(Enum lookup, string name, string defaultFormat) { - + ChatBoxManager.RegisterState(SerialisedName, lookup.ToLookup(), name, defaultFormat); } protected void ChangeStateTo(Enum lookup) { + ChatBoxManager.ChangeStateTo(SerialisedName, lookup.ToLookup()); + } + + protected void CreateEvent(Enum lookup, string name, string defaultFormat, int defaultLength) + { + ChatBoxManager.RegisterEvent(SerialisedName, lookup.ToLookup(), name, defaultFormat, defaultLength); + } + protected void TriggerEvent(Enum lookup) + { + ChatBoxManager.TriggerEvent(SerialisedName, lookup.ToLookup()); } } diff --git a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs index 4a5b6548..83816057 100644 --- a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs +++ b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs @@ -21,6 +21,7 @@ public class WindowsMediaProvider public GlobalSystemMediaTransportControlsSession? Controller => sessionManager?.GetCurrentSession(); public Action? OnPlaybackStateUpdate; + public Action? OnTrackChange; public MediaState State { get; private set; } = null!; public async Task Hook() @@ -66,6 +67,8 @@ private void onAnyMediaPropertyChanged(GlobalSystemMediaTransportControlsSession State.Title = args.Title; State.Artist = args.Artist; + + OnTrackChange?.Invoke(); } private void onAnyTimelinePropertiesChanged(GlobalSystemMediaTransportControlsSession session, GlobalSystemMediaTransportControlsSessionTimelineProperties args) diff --git a/VRCOSC.Modules/AFK/AFKModule.cs b/VRCOSC.Modules/AFK/AFKModule.cs index e8bb6d31..886683bb 100644 --- a/VRCOSC.Modules/AFK/AFKModule.cs +++ b/VRCOSC.Modules/AFK/AFKModule.cs @@ -26,6 +26,8 @@ protected override void CreateAttributes() protected override void OnModuleStart() { afkBegan = null; + + ChangeStateTo(AFKModuleState.NotAFK); } protected override void OnModuleUpdate() diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index d6457b02..e547df40 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -26,6 +26,7 @@ public class MediaModule : ChatBoxModule public MediaModule() { mediaProvider.OnPlaybackStateUpdate += onPlaybackStateUpdate; + mediaProvider.OnTrackChange += onTrackChange; } protected override void CreateAttributes() @@ -44,6 +45,8 @@ protected override void CreateAttributes() CreateState(MediaState.Playing, "Playing", @"[{time}/{duration}] Now Playing: {artist} - {title}"); CreateState(MediaState.Paused, "Paused", @"[Paused]"); + CreateEvent(MediaEvent.NowPlaying, "Now Playing", @"[Now Playing] {artist} - {title}", 5); + CreateVariable(MediaVariable.Title, @"Title", @"{title}"); CreateVariable(MediaVariable.Artist, @"Artist", @"{artist}"); CreateVariable(MediaVariable.Time, @"Time", @"{time}"); @@ -93,9 +96,14 @@ protected override void OnModuleUpdate() { if (mediaProvider.Controller is not null) sendUpdatableParameters(); + updateVariables(); + } + + private void updateVariables() + { SetVariableValue(MediaVariable.Title, mediaProvider.State.Title); SetVariableValue(MediaVariable.Artist, mediaProvider.State.Artist); - SetVariableValue(MediaVariable.Title, mediaProvider.State.Position?.Position.ToString(@"mm\:ss")); + SetVariableValue(MediaVariable.Time, mediaProvider.State.Position?.Position.ToString(@"mm\:ss")); SetVariableValue(MediaVariable.Duration, mediaProvider.State.Position?.EndTime.ToString(@"mm\:ss")); SetVariableValue(MediaVariable.Volume, (mediaProvider.State.Volume * 100).ToString("##0")); } @@ -103,6 +111,13 @@ protected override void OnModuleUpdate() private void onPlaybackStateUpdate() { sendMediaParameters(); + ChangeStateTo(mediaProvider.State.IsPlaying ? MediaState.Playing : MediaState.Paused); + } + + private void onTrackChange() + { + updateVariables(); + TriggerEvent(MediaEvent.NowPlaying); } private void sendMediaParameters() @@ -199,6 +214,11 @@ private enum MediaState Paused } + private enum MediaEvent + { + NowPlaying + } + private enum MediaVariable { Title, From 8ea6d496342b1db9ff4b52571d3408ba0aa63340 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 15:57:45 +0100 Subject: [PATCH 30/59] Implement state choosing logic --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 100 +++++++++++----- VRCOSC.Game/ChatBox/Clips/Clip.cs | 107 +++++++++++++++--- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 4 +- .../ChatBox/Clips/CompoundClipState.cs | 11 -- .../SelectedClipModuleSelector.cs | 4 +- VRCOSC.Game/Modules/GameManager.cs | 14 ++- VRCOSC.Modules/Media/MediaModule.cs | 1 + 7 files changed, 180 insertions(+), 61 deletions(-) delete mode 100644 VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index a58a063e..22cf404e 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using VRCOSC.Game.ChatBox.Clips; namespace VRCOSC.Game.ChatBox; @@ -18,14 +19,29 @@ public class ChatBoxManager public readonly Dictionary> Events = new(); public readonly Dictionary ModuleStates = new(); - - // TODO - module events will become true for 1 update check and then reset to false - // Each clip evaluation that checks if an event has occured will have to locally store a DateTimeOffset - public readonly Dictionary> ModuleEvents = new(); + public readonly List<(string, string)> ModuleEvents = new(); + public IReadOnlyDictionary ModuleEnabledStore => moduleEnabledStore; public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); + private bool sendEnabled; + + public bool SendEnabled + { + get => sendEnabled; + set + { + if (sendEnabled && !value) clearChatBox(); + sendEnabled = value; + } + } + public float Resolution => 1f / (float)TimelineLength.Value.TotalSeconds; + public int CurrentSecond => (int)Math.Floor((DateTimeOffset.Now - startTime).TotalSeconds) % (int)TimelineLength.Value.TotalSeconds; + + private DateTimeOffset startTime; + private Dictionary moduleEnabledStore = null!; + private bool isClear = true; public ChatBoxManager() { @@ -35,23 +51,63 @@ public ChatBoxManager() Clips.Add(clip = new Clip(this) { - Priority = { Value = i }, + Priority = { Value = i } }); - clip.AssociatedModules.Add("Test " + i); } } public Clip CreateClip() => new(this); - public void Initialise() + public void Initialise(Dictionary moduleEnabled) { + startTime = DateTimeOffset.Now; + moduleEnabledStore = moduleEnabled; } public void Update() { - // evaluate clips + Clips.ForEach(clip => clip.Update()); + + Clip? validClip = null; - // reset module event triggers + for (var i = 5; i >= 0; i--) + { + Clips.Where(clip => clip.Priority.Value == i).ForEach(clip => + { + if (!clip.Evalulate() || validClip is not null) return; + + validClip = clip; + }); + + if (validClip is not null) break; + } + + handleClip(validClip); + + ModuleEvents.Clear(); + } + + public void Shutdown() + { + ModuleEvents.Clear(); + } + + private void handleClip(Clip? clip) + { + if (clip is null && !isClear) + { + clearChatBox(); + return; + } + + if (clip is null) return; + + // format clip and send to ChatBox + } + + private void clearChatBox() + { + isClear = true; } public bool IncreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value + 1); @@ -96,7 +152,8 @@ public void RegisterState(string module, string lookup, string name, string defa { var state = new ClipState { - Lookup = lookup, + Modules = new List { module }, + States = new List { lookup }, Name = name, Format = { Value = defaultFormat } }; @@ -126,7 +183,8 @@ public void RegisterEvent(string module, string lookup, string name, string defa { var clipEvent = new ClipEvent { - Lookup = lookup, + Modules = new List { module }, + States = new List { lookup }, Name = name, Format = { Value = defaultFormat }, Length = { Value = defaultLength } @@ -141,29 +199,11 @@ public void RegisterEvent(string module, string lookup, string name, string defa Events.Add(module, new Dictionary()); Events[module].Add(lookup, clipEvent); } - - if (ModuleEvents.TryGetValue(module, out var innerDict2)) - { - innerDict2[lookup] = false; - } - else - { - ModuleEvents.Add(module, new Dictionary()); - - if (ModuleEvents[module].ContainsKey(lookup)) - { - ModuleEvents[module][lookup] = false; - } - else - { - ModuleEvents[module].Add(lookup, false); - } - } } public void TriggerEvent(string module, string lookup) { - ModuleEvents[module][lookup] = true; + ModuleEvents.Add((module, lookup)); } public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index f08a4a3f..1d32ef3a 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; namespace VRCOSC.Game.ChatBox.Clips; @@ -19,13 +21,13 @@ public class Clip public readonly BindableNumber Priority = new(); public readonly BindableList AssociatedModules = new(); public readonly BindableList AvailableVariables = new(); - public readonly BindableDictionary> States = new(); - public readonly BindableDictionary> Events = new(); + public readonly BindableList States = new(); + public readonly BindableList Events = new(); public readonly Bindable Start = new(); public readonly Bindable End = new(30); public int Length => End.Value - Start.Value; - public readonly Dictionary> ModuleEvents = new(); + public readonly List ModuleEvents = new(); private readonly ChatBoxManager chatBoxManager; @@ -37,17 +39,95 @@ public Clip(ChatBoxManager chatBoxManager) public void Update() { - // check for events + chatBoxManager.ModuleEvents.ForEach(moduleEvent => + { + var (module, lookup) = moduleEvent; + var clipEvent = Events[module][lookup]; + ModuleEvents.Add(DateTimeOffset.Now + TimeSpan.FromSeconds(clipEvent.Length.Value)); + }); + + ModuleEvents.ForEach(moduleEvent => + { + if (moduleEvent <= DateTimeOffset.Now) ModuleEvents.Remove(moduleEvent); + }); } public bool Evalulate() { if (!Enabled.Value) return false; + if (Start.Value > chatBoxManager.CurrentSecond || End.Value <= chatBoxManager.CurrentSecond) return false; + if (ModuleEvents.Any()) return true; + + var localStates = getCopyOfStates(); + removeDisabledModules(localStates); + removeLessCompoundedStates(localStates); + removeInvalidStates(localStates); + + Debug.Assert(localStates.Count == 1); + + var chosenState = localStates.First(); + return chosenState.Enabled.Value; + } + + private List getCopyOfStates() + { + var localStates = new List(); + States.ForEach(state => localStates.Add(state)); + return localStates; + } + + private void removeDisabledModules(List localStates) + { + var statesToRemove = new List(); + + foreach (ClipState clipState in localStates) + { + var stateValid = clipState.Modules.All(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]); + if (!stateValid) statesToRemove.Add(clipState); + } + + statesToRemove.ForEach(moduleName => localStates.Remove(moduleName)); + } + + private void removeLessCompoundedStates(List localStates) + { + var enabledAndAssociatedModules = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]).ToList(); + enabledAndAssociatedModules.Sort(); + + var statesToRemove = new List(); + + localStates.ForEach(clipState => + { + var clipStateModules = clipState.Modules; + clipStateModules.Sort(); + + if (!clipStateModules.SequenceEqual(enabledAndAssociatedModules)) statesToRemove.Add(clipState); + }); + + statesToRemove.ForEach(clipState => localStates.Remove(clipState)); + } + + private void removeInvalidStates(List localStates) + { + var currentStates = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]).Select(moduleName => chatBoxManager.ModuleStates[moduleName]).ToList(); + currentStates.Sort(); - // check states and compound states for what state we're currently in and check to see if that state is enabled - // check if there are any ongoing events + var statesToRemove = new List(); - return true; + localStates.ForEach(clipState => + { + var clipStateStates = clipState.States; + clipStateStates.Sort(); + + if (!clipStateStates.SequenceEqual(currentStates)) statesToRemove.Add(clipState); + }); + + statesToRemove.ForEach(clipState => localStates.Remove(clipState)); + } + + public void GetFormat() + { + // return events if one exists before returning chosen state } private void onAssociatedModulesChanged(NotifyCollectionChangedEventArgs e) @@ -101,13 +181,14 @@ private void populateEvents(NotifyCollectionChangedEventArgs e) { foreach (string newModule in e.NewItems) { - var events = chatBoxManager.Events[newModule]; - - Events.Add(newModule, new Dictionary()); - - foreach (var (key, value) in events) + if (chatBoxManager.Events.TryGetValue(newModule, out var events)) { - Events[newModule][key] = value; + Events.Add(newModule, new Dictionary()); + + foreach (var (key, value) in events) + { + Events[newModule][key] = value; + } } } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index 5a325dce..5f0e3b42 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -1,13 +1,15 @@ // 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.Bindables; namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { - public required string Lookup { get; init; } + public required List Modules { get; init; } + public required List States { get; init; } public required string Name { get; init; } public Bindable Format = new(); public BindableBool Enabled = new(); diff --git a/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs b/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs deleted file mode 100644 index 1641c1f8..00000000 --- a/VRCOSC.Game/ChatBox/Clips/CompoundClipState.cs +++ /dev/null @@ -1,11 +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.Collections.Generic; - -namespace VRCOSC.Game.ChatBox.Clips; - -public class CompoundClipState -{ - private List states = new(); -} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index 54e95c7b..aa0d5286 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Modules; +using VRCOSC.Game.Modules.ChatBox; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -85,7 +87,7 @@ private void load() moduleFlow.Clear(); - foreach (var module in gameManager.ModuleManager) + foreach (var module in gameManager.ModuleManager.Where(module => module.GetType().IsSubclassOf(typeof(ChatBoxModule)))) { DrawableAssociatedModule drawableAssociatedModule; diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 0d9b41fb..629f7198 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -116,7 +117,7 @@ protected override void Update() ModuleManager.Update(); - //ChatBoxInterface.Update(); + ChatBoxManager.Update(); } protected override void LoadComplete() @@ -163,7 +164,7 @@ private void handleOscDataCache() switch (data.ParameterName) { case @"VRCOSC/Controls/ChatBox": - //ChatBoxInterface.SendEnabled = (bool)data.ParameterValue; + ChatBoxManager.SendEnabled = (bool)data.ParameterValue; break; } } @@ -203,13 +204,16 @@ private async Task startAsync() return; } + var moduleEnabled = new Dictionary(); + ModuleManager.ForEach(module => moduleEnabled.Add(module.SerialisedName, module.Enabled.Value)); + State.Value = GameManagerState.Starting; await Task.Delay(startstop_delay); VRChatOscClient.Enable(OscClientFlag.Send); Player.Initialise(); - //ChatBoxInterface.Initialise(); + ChatBoxManager.Initialise(moduleEnabled); sendControlValues(); ModuleManager.Start(); VRChatOscClient.Enable(OscClientFlag.Receive); @@ -238,7 +242,7 @@ private async Task stopAsync() await OSCRouter.Disable(); ModuleManager.Stop(); - //ChatBoxInterface.Shutdown(); + ChatBoxManager.Shutdown(); Player.ResetAll(); await VRChatOscClient.Disable(OscClientFlag.Send); @@ -309,7 +313,7 @@ private void checkForVRChat() private void sendControlValues() { - //VRChatOscClient.SendValue(@$"{VRChatOscConstants.ADDRESS_AVATAR_PARAMETERS_PREFIX}/VRCOSC/Controls/ChatBox", ChatBoxInterface.SendEnabled); + VRChatOscClient.SendValue(@$"{VRChatOscConstants.ADDRESS_AVATAR_PARAMETERS_PREFIX}/VRCOSC/Controls/ChatBox", ChatBoxManager.SendEnabled); } } diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index e547df40..23bdee5f 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -31,6 +31,7 @@ public MediaModule() protected override void CreateAttributes() { + // TODO - Remove into a separate screen that allows for start options 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); CreateParameter(MediaParameter.Play, ParameterMode.ReadWrite, @"VRCOSC/Media/Play", "Play/Pause", @"True for playing. False for paused"); From 4474de05ab2e60ad33d28e31b3de4ca49e1f1165 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 17:45:19 +0100 Subject: [PATCH 31/59] Implement compound state generation --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 10 ++--- VRCOSC.Game/ChatBox/Clips/Clip.cs | 61 +++++++++++++------------- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 7 ++- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 13 ++++-- 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 22cf404e..51a82774 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.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; @@ -152,9 +152,7 @@ public void RegisterState(string module, string lookup, string name, string defa { var state = new ClipState { - Modules = new List { module }, - States = new List { lookup }, - Name = name, + States = new List<(string, string)> { (module, lookup) }, Format = { Value = defaultFormat } }; @@ -183,8 +181,8 @@ public void RegisterEvent(string module, string lookup, string name, string defa { var clipEvent = new ClipEvent { - Modules = new List { module }, - States = new List { lookup }, + Module = module, + Lookup = lookup, Name = name, Format = { Value = defaultFormat }, Length = { Value = defaultLength } diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 1d32ef3a..bd08fd1a 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -6,6 +6,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using NuGet.Packaging; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -27,8 +28,6 @@ public class Clip public readonly Bindable End = new(30); public int Length => End.Value - Start.Value; - public readonly List ModuleEvents = new(); - private readonly ChatBoxManager chatBoxManager; public Clip(ChatBoxManager chatBoxManager) @@ -58,7 +57,7 @@ public bool Evalulate() if (Start.Value > chatBoxManager.CurrentSecond || End.Value <= chatBoxManager.CurrentSecond) return false; if (ModuleEvents.Any()) return true; - var localStates = getCopyOfStates(); + var localStates = States.Select(state => state.Copy()).ToList(); removeDisabledModules(localStates); removeLessCompoundedStates(localStates); removeInvalidStates(localStates); @@ -69,20 +68,13 @@ public bool Evalulate() return chosenState.Enabled.Value; } - private List getCopyOfStates() - { - var localStates = new List(); - States.ForEach(state => localStates.Add(state)); - return localStates; - } - private void removeDisabledModules(List localStates) { var statesToRemove = new List(); foreach (ClipState clipState in localStates) { - var stateValid = clipState.Modules.All(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]); + var stateValid = clipState.ModuleNames.All(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]); if (!stateValid) statesToRemove.Add(clipState); } @@ -98,7 +90,7 @@ private void removeLessCompoundedStates(List localStates) localStates.ForEach(clipState => { - var clipStateModules = clipState.Modules; + var clipStateModules = clipState.ModuleNames; clipStateModules.Sort(); if (!clipStateModules.SequenceEqual(enabledAndAssociatedModules)) statesToRemove.Add(clipState); @@ -116,7 +108,7 @@ private void removeInvalidStates(List localStates) localStates.ForEach(clipState => { - var clipStateStates = clipState.States; + var clipStateStates = clipState.StateNames; clipStateStates.Sort(); if (!clipStateStates.SequenceEqual(currentStates)) statesToRemove.Add(clipState); @@ -149,30 +141,39 @@ private void populateAvailableVariables() private void populateStates(NotifyCollectionChangedEventArgs e) { - if (e.NewItems is not null) - { - foreach (string newModule in e.NewItems) - { - var states = chatBoxManager.States[newModule]; - - States.Add(newModule, new Dictionary()); + if (e.OldItems is not null) removeStatesOfRemovedModules(e); + if (e.NewItems is not null) addStatesOfAddedModules(e); + } - foreach (var (key, value) in states) - { - States[newModule][key] = value; - } - } + private void removeStatesOfRemovedModules(NotifyCollectionChangedEventArgs e) + { + foreach (string oldModule in e.OldItems!) + { + States.RemoveAll(clipState => clipState.ModuleNames.Contains(oldModule)); } + } - if (e.OldItems is not null) + private void addStatesOfAddedModules(NotifyCollectionChangedEventArgs e) + { + foreach (string moduleName in e.NewItems!) { - foreach (string oldModule in e.OldItems) + var statesPrevious = States.Select(clipState => clipState.Copy()).ToList(); + + var states = chatBoxManager.States[moduleName]; + + foreach (var (stateName, clipState) in states) { - States.Remove(oldModule); + var statesPreviousLocal = statesPrevious.Select(clipStateLocal => clipStateLocal.Copy()).ToList(); + + statesPreviousLocal.ForEach(localState => + { + localState.States.Add((moduleName, stateName)); + }); + + States.AddRange(statesPreviousLocal); + States.Add(clipState); } } - - // compound state calculation } private void populateEvents(NotifyCollectionChangedEventArgs e) diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index ad0692c3..85b41440 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -5,7 +5,12 @@ namespace VRCOSC.Game.ChatBox.Clips; -public class ClipEvent : ClipState +public class ClipEvent { + public required string Module { get; init; } + public required string Lookup { get; init; } + public required string Name { get; init; } + public Bindable Format = new(); + public BindableBool Enabled = new(); public Bindable Length = new(); } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index 5f0e3b42..472a73ec 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -2,15 +2,22 @@ // See the LICENSE file in the repository root for full license text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { - public required List Modules { get; init; } - public required List States { get; init; } - public required string Name { get; init; } + public required List<(string, string)> States { get; init; } public Bindable Format = new(); public BindableBool Enabled = new(); + + public List ModuleNames => States.Select(state => state.Item1).ToList(); + public List StateNames => States.Select(state => state.Item2).ToList(); + + public ClipState Copy() => new() + { + States = States + }; } From 74fdee62ed7881c5f3fd6f31dc95609211b88325 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 18:04:43 +0100 Subject: [PATCH 32/59] Add event generation and ensure copies of references are being used --- VRCOSC.Game/ChatBox/Clips/Clip.cs | 54 ++++++++++++----------- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 9 ++++ VRCOSC.Game/ChatBox/Clips/ClipVariable.cs | 8 ++++ 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index bd08fd1a..7ac256df 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -6,9 +6,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using NuGet.Packaging; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; namespace VRCOSC.Game.ChatBox.Clips; @@ -30,6 +28,8 @@ public class Clip private readonly ChatBoxManager chatBoxManager; + private ((string, string), DateTimeOffset)? currentEvent; + public Clip(ChatBoxManager chatBoxManager) { this.chatBoxManager = chatBoxManager; @@ -41,21 +41,22 @@ public void Update() chatBoxManager.ModuleEvents.ForEach(moduleEvent => { var (module, lookup) = moduleEvent; - var clipEvent = Events[module][lookup]; - ModuleEvents.Add(DateTimeOffset.Now + TimeSpan.FromSeconds(clipEvent.Length.Value)); - }); - ModuleEvents.ForEach(moduleEvent => - { - if (moduleEvent <= DateTimeOffset.Now) ModuleEvents.Remove(moduleEvent); + // TODO if new event's module name is equal to the current event's module name, it should replace + // TODO if new event's module name is different, add to a queue to be put into current event when current even expires + + var clipEvent = Events.Where(clipEvent => clipEvent.Module == module && clipEvent.Lookup == lookup); }); + + if (currentEvent?.Item2 <= DateTimeOffset.Now) currentEvent = null; } public bool Evalulate() { if (!Enabled.Value) return false; if (Start.Value > chatBoxManager.CurrentSecond || End.Value <= chatBoxManager.CurrentSecond) return false; - if (ModuleEvents.Any()) return true; + + if (currentEvent is not null) return true; var localStates = States.Select(state => state.Copy()).ToList(); removeDisabledModules(localStates); @@ -135,7 +136,8 @@ private void populateAvailableVariables() foreach (var module in AssociatedModules) { - AvailableVariables.AddRange(chatBoxManager.Variables[module].Values.ToList()); + var clipVariables = chatBoxManager.Variables[module].Values.Select(clipVariable => clipVariable.Copy()).ToList(); + AvailableVariables.AddRange(clipVariables); } } @@ -178,27 +180,27 @@ private void addStatesOfAddedModules(NotifyCollectionChangedEventArgs e) private void populateEvents(NotifyCollectionChangedEventArgs e) { - if (e.NewItems is not null) - { - foreach (string newModule in e.NewItems) - { - if (chatBoxManager.Events.TryGetValue(newModule, out var events)) - { - Events.Add(newModule, new Dictionary()); + if (e.OldItems is not null) removeEventsOfRemovedModules(e); + if (e.NewItems is not null) addEventsOfAddedModules(e); + } - foreach (var (key, value) in events) - { - Events[newModule][key] = value; - } - } - } + private void removeEventsOfRemovedModules(NotifyCollectionChangedEventArgs e) + { + foreach (string oldModule in e.OldItems!) + { + Events.RemoveAll(clipEvent => clipEvent.Module == oldModule); } + } - if (e.OldItems is not null) + private void addEventsOfAddedModules(NotifyCollectionChangedEventArgs e) + { + foreach (string moduleName in e.NewItems!) { - foreach (string oldModule in e.OldItems) + if (!chatBoxManager.Events.TryGetValue(moduleName, out var events)) continue; + + foreach (var (_, clipEvent) in events) { - Events.Remove(oldModule); + Events.Add(clipEvent.Copy()); } } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index 85b41440..bcb46802 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -13,4 +13,13 @@ public class ClipEvent public Bindable Format = new(); public BindableBool Enabled = new(); public Bindable Length = new(); + + public ClipEvent Copy() => new() + { + Module = Module, + Lookup = Lookup, + Name = Name, + Format = Format, + Length = Length + }; } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index 9fd87db7..e7a7c6ae 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -13,4 +13,12 @@ public class ClipVariable public required string Name { get; init; } public required string Format { get; init; } public string? Value { get; set; } + + public ClipVariable Copy() => new() + { + Module = Module, + Lookup = Lookup, + Name = Name, + Format = Format + }; } From 8e9340f935a0159aa64316f9349660dc44e534e3 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 18:22:04 +0100 Subject: [PATCH 33/59] Implement variable value replacement --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 7 +++- VRCOSC.Game/ChatBox/Clips/Clip.cs | 42 ++++++++++++++++++++--- VRCOSC.Game/ChatBox/Clips/ClipVariable.cs | 1 - 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 51a82774..1cc0835c 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -18,6 +18,7 @@ public class ChatBoxManager public readonly Dictionary> States = new(); public readonly Dictionary> Events = new(); + public readonly Dictionary<(string, string), string?> ModuleVariables = new(); public readonly Dictionary ModuleStates = new(); public readonly List<(string, string)> ModuleEvents = new(); public IReadOnlyDictionary ModuleEnabledStore => moduleEnabledStore; @@ -62,6 +63,8 @@ public void Initialise(Dictionary moduleEnabled) { startTime = DateTimeOffset.Now; moduleEnabledStore = moduleEnabled; + + Clips.ForEach(clip => clip.Initialise()); } public void Update() @@ -90,6 +93,8 @@ public void Update() public void Shutdown() { ModuleEvents.Clear(); + ModuleVariables.Clear(); + ModuleStates.Clear(); } private void handleClip(Clip? clip) @@ -145,7 +150,7 @@ public void RegisterVariable(string module, string lookup, string name, string f public void SetVariable(string module, string lookup, string? value) { - Variables[module][lookup].Value = value; + ModuleVariables[(module, lookup)] = value; } public void RegisterState(string module, string lookup, string name, string defaultFormat) diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 7ac256df..7b26d9a2 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; namespace VRCOSC.Game.ChatBox.Clips; @@ -28,7 +29,9 @@ public class Clip private readonly ChatBoxManager chatBoxManager; + // TODO turn into struct private ((string, string), DateTimeOffset)? currentEvent; + private ClipState? currentState; public Clip(ChatBoxManager chatBoxManager) { @@ -36,6 +39,12 @@ public Clip(ChatBoxManager chatBoxManager) AssociatedModules.BindCollectionChanged((_, e) => onAssociatedModulesChanged(e), true); } + public void Initialise() + { + currentEvent = null; + currentState = null; + } + public void Update() { chatBoxManager.ModuleEvents.ForEach(moduleEvent => @@ -63,9 +72,13 @@ public bool Evalulate() removeLessCompoundedStates(localStates); removeInvalidStates(localStates); - Debug.Assert(localStates.Count == 1); + Debug.Assert(localStates.Count is 0 or 1); + + if (!localStates.Any()) return false; var chosenState = localStates.First(); + + currentState = chosenState.Enabled.Value ? chosenState : null; return chosenState.Enabled.Value; } @@ -102,9 +115,11 @@ private void removeLessCompoundedStates(List localStates) private void removeInvalidStates(List localStates) { - var currentStates = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]).Select(moduleName => chatBoxManager.ModuleStates[moduleName]).ToList(); + var currentStates = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName] && chatBoxManager.ModuleStates.TryGetValue(moduleName, out _)).Select(moduleName => chatBoxManager.ModuleStates[moduleName]).ToList(); currentStates.Sort(); + if (!currentStates.Any()) return; + var statesToRemove = new List(); localStates.ForEach(clipState => @@ -118,9 +133,28 @@ private void removeInvalidStates(List localStates) statesToRemove.ForEach(clipState => localStates.Remove(clipState)); } - public void GetFormat() + public string GetFormattedText() { - // return events if one exists before returning chosen state + if (currentEvent is not null) + return formatText(Events.Single(clipEvent => clipEvent.Module == currentEvent.Value.Item1.Item1 && clipEvent.Lookup == currentEvent.Value.Item1.Item2)); + + return formatText(currentState!); + } + + private string formatText(ClipState clipState) => formatText(clipState.Format.Value); + private string formatText(ClipEvent clipEvent) => formatText(clipEvent.Format.Value); + + private string formatText(string text) + { + var returnText = text; + + AvailableVariables.ForEach(clipVariable => + { + var variableValue = chatBoxManager.ModuleVariables[(clipVariable.Module, clipVariable.Lookup)] ?? string.Empty; + returnText = returnText.Replace(clipVariable.Format, variableValue); + }); + + return returnText; } private void onAssociatedModulesChanged(NotifyCollectionChangedEventArgs e) diff --git a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index e7a7c6ae..53eb5a17 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -12,7 +12,6 @@ public class ClipVariable public required string Lookup { get; init; } public required string Name { get; init; } public required string Format { get; init; } - public string? Value { get; set; } public ClipVariable Copy() => new() { From c659facbc183929eac7b5f543ee6cf11852904d2 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 19:18:12 +0100 Subject: [PATCH 34/59] Documentation and notes + required module state changes --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 103 ++++++++++-------- VRCOSC.Game/ChatBox/Clips/Clip.cs | 20 ++-- VRCOSC.Game/ChatBox/Clips/ClipVariable.cs | 8 -- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 10 +- .../ChatBox/Timeline/TimelineEditor.cs | 2 +- .../ChatBoxText/ChatBoxTextModule.cs | 1 + VRCOSC.Modules/Clock/ClockModule.cs | 5 + .../HardwareStats/HardwareStatsModule.cs | 10 +- VRCOSC.Modules/Heartrate/HeartRateModule.cs | 3 +- VRCOSC.Modules/Media/MediaModule.cs | 6 +- .../OpenVR/OpenVRStatisticsModule.cs | 5 + VRCOSC.Modules/Weather/WeatherModule.cs | 16 ++- 12 files changed, 107 insertions(+), 82 deletions(-) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 1cc0835c..3987d258 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -12,18 +12,7 @@ namespace VRCOSC.Game.ChatBox; public class ChatBoxManager { - public readonly BindableList Clips = new(); - - public readonly Dictionary> Variables = new(); - public readonly Dictionary> States = new(); - public readonly Dictionary> Events = new(); - - public readonly Dictionary<(string, string), string?> ModuleVariables = new(); - public readonly Dictionary ModuleStates = new(); - public readonly List<(string, string)> ModuleEvents = new(); - public IReadOnlyDictionary ModuleEnabledStore => moduleEnabledStore; - - public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); + private const int priority_count = 6; private bool sendEnabled; @@ -37,20 +26,41 @@ public bool SendEnabled } } - public float Resolution => 1f / (float)TimelineLength.Value.TotalSeconds; + public readonly BindableList Clips = new(); + + // TODO Might be best to refactor all this into metadata vs data. The template stuff should be the metadata which gets referenced by all the clips. The stored data for each state and event gets stored in the clips + // Dictionary> + public readonly Dictionary> TemplateVariables = new(); + public readonly Dictionary> TemplateStates = new(); + public readonly Dictionary> TemplateEvents = new(); + public IReadOnlyDictionary ModuleEnabledCache = null!; + + // TODO These still get stored here since they're shared between clips for evaluation, but could be abstracted behind some helper methods to make things cleaner + // Dictionary> + public readonly Dictionary> VariableValues = new(); + + // Dictionary + public readonly Dictionary StateValues = new(); + + /// + /// The events occuring in the current update. Gets cleared after all Clips have been updated to ensure the event is only handled once + /// + // List + public readonly List<(string, string)> TriggeredEvents = new(); + + public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); + public float TimelineResolution => 1f / (float)TimelineLength.Value.TotalSeconds; + public int CurrentSecond => (int)Math.Floor((DateTimeOffset.Now - startTime).TotalSeconds) % (int)TimelineLength.Value.TotalSeconds; private DateTimeOffset startTime; - private Dictionary moduleEnabledStore = null!; - private bool isClear = true; + private bool isClear; public ChatBoxManager() { - for (var i = 0; i < 6; i++) + for (var i = 0; i < priority_count; i++) { - Clip clip; - - Clips.Add(clip = new Clip(this) + Clips.Add(new Clip(this) { Priority = { Value = i } }); @@ -59,10 +69,11 @@ public ChatBoxManager() public Clip CreateClip() => new(this); - public void Initialise(Dictionary moduleEnabled) + public void Initialise(Dictionary moduleEnabledCache) { startTime = DateTimeOffset.Now; - moduleEnabledStore = moduleEnabled; + isClear = true; + ModuleEnabledCache = moduleEnabledCache; Clips.ForEach(clip => clip.Initialise()); } @@ -73,7 +84,7 @@ public void Update() Clip? validClip = null; - for (var i = 5; i >= 0; i--) + for (var i = priority_count - 1; i >= 0; i--) { Clips.Where(clip => clip.Priority.Value == i).ForEach(clip => { @@ -87,14 +98,14 @@ public void Update() handleClip(validClip); - ModuleEvents.Clear(); + TriggeredEvents.Clear(); } public void Shutdown() { - ModuleEvents.Clear(); - ModuleVariables.Clear(); - ModuleStates.Clear(); + TriggeredEvents.Clear(); + VariableValues.Clear(); + StateValues.Clear(); } private void handleClip(Clip? clip) @@ -115,16 +126,15 @@ private void clearChatBox() isClear = true; } - public bool IncreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value + 1); - public bool DecreasePriority(Clip clip) => SetPriority(clip, clip.Priority.Value - 1); + public void IncreasePriority(Clip clip) => setPriority(clip, clip.Priority.Value + 1); + public void DecreasePriority(Clip clip) => setPriority(clip, clip.Priority.Value - 1); - public bool SetPriority(Clip clip, int priority) + private void setPriority(Clip clip, int priority) { - if (priority is > 5 or < 0) return false; - if (RetrieveClipsWithPriority(priority).Any(clip.Intersects)) return false; + if (priority is > priority_count - 1 or < 0) return; + if (RetrieveClipsWithPriority(priority).Any(clip.Intersects)) return; clip.Priority.Value = priority; - return true; } public void RegisterVariable(string module, string lookup, string name, string format) @@ -137,20 +147,21 @@ public void RegisterVariable(string module, string lookup, string name, string f Format = format }; - if (Variables.TryGetValue(module, out var innerDict)) + if (TemplateVariables.TryGetValue(module, out var innerDict)) { innerDict.Add(lookup, variable); } else { - Variables.Add(module, new Dictionary()); - Variables[module].Add(lookup, variable); + TemplateVariables.Add(module, new Dictionary()); + TemplateVariables[module].Add(lookup, variable); } } public void SetVariable(string module, string lookup, string? value) { - ModuleVariables[(module, lookup)] = value; + if (!VariableValues.ContainsKey(module)) VariableValues.Add(module, new Dictionary()); + VariableValues[module][lookup] = value; } public void RegisterState(string module, string lookup, string name, string defaultFormat) @@ -161,25 +172,25 @@ public void RegisterState(string module, string lookup, string name, string defa Format = { Value = defaultFormat } }; - if (States.TryGetValue(module, out var innerDict)) + if (TemplateStates.TryGetValue(module, out var innerDict)) { innerDict.Add(lookup, state); } else { - States.Add(module, new Dictionary()); - States[module].Add(lookup, state); + TemplateStates.Add(module, new Dictionary()); + TemplateStates[module].Add(lookup, state); } - if (!ModuleStates.TryAdd(module, lookup)) + if (!StateValues.TryAdd(module, lookup)) { - ModuleStates[module] = lookup; + StateValues[module] = lookup; } } public void ChangeStateTo(string module, string lookup) { - ModuleStates[module] = lookup; + StateValues[module] = lookup; } public void RegisterEvent(string module, string lookup, string name, string defaultFormat, int defaultLength) @@ -193,20 +204,20 @@ public void RegisterEvent(string module, string lookup, string name, string defa Length = { Value = defaultLength } }; - if (Events.TryGetValue(module, out var innerDict)) + if (TemplateEvents.TryGetValue(module, out var innerDict)) { innerDict.Add(lookup, clipEvent); } else { - Events.Add(module, new Dictionary()); - Events[module].Add(lookup, clipEvent); + TemplateEvents.Add(module, new Dictionary()); + TemplateEvents[module].Add(lookup, clipEvent); } } public void TriggerEvent(string module, string lookup) { - ModuleEvents.Add((module, lookup)); + TriggeredEvents.Add((module, lookup)); } public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 7b26d9a2..c1956392 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -29,7 +29,7 @@ public class Clip private readonly ChatBoxManager chatBoxManager; - // TODO turn into struct + // TODO Dumb just store the ClipEvent somehow private ((string, string), DateTimeOffset)? currentEvent; private ClipState? currentState; @@ -47,12 +47,12 @@ public void Initialise() public void Update() { - chatBoxManager.ModuleEvents.ForEach(moduleEvent => + chatBoxManager.TriggeredEvents.ForEach(moduleEvent => { var (module, lookup) = moduleEvent; // TODO if new event's module name is equal to the current event's module name, it should replace - // TODO if new event's module name is different, add to a queue to be put into current event when current even expires + // TODO if new event's module name is different, add to a queue to be put into current event when current event expires var clipEvent = Events.Where(clipEvent => clipEvent.Module == module && clipEvent.Lookup == lookup); }); @@ -88,7 +88,7 @@ private void removeDisabledModules(List localStates) foreach (ClipState clipState in localStates) { - var stateValid = clipState.ModuleNames.All(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]); + var stateValid = clipState.ModuleNames.All(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]); if (!stateValid) statesToRemove.Add(clipState); } @@ -97,7 +97,7 @@ private void removeDisabledModules(List localStates) private void removeLessCompoundedStates(List localStates) { - var enabledAndAssociatedModules = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName]).ToList(); + var enabledAndAssociatedModules = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]).ToList(); enabledAndAssociatedModules.Sort(); var statesToRemove = new List(); @@ -115,7 +115,7 @@ private void removeLessCompoundedStates(List localStates) private void removeInvalidStates(List localStates) { - var currentStates = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledStore[moduleName] && chatBoxManager.ModuleStates.TryGetValue(moduleName, out _)).Select(moduleName => chatBoxManager.ModuleStates[moduleName]).ToList(); + var currentStates = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledCache[moduleName] && chatBoxManager.StateValues.TryGetValue(moduleName, out _)).Select(moduleName => chatBoxManager.StateValues[moduleName]).ToList(); currentStates.Sort(); if (!currentStates.Any()) return; @@ -150,7 +150,7 @@ private string formatText(string text) AvailableVariables.ForEach(clipVariable => { - var variableValue = chatBoxManager.ModuleVariables[(clipVariable.Module, clipVariable.Lookup)] ?? string.Empty; + var variableValue = chatBoxManager.VariableValues[clipVariable.Module][clipVariable.Lookup] ?? string.Empty; returnText = returnText.Replace(clipVariable.Format, variableValue); }); @@ -170,7 +170,7 @@ private void populateAvailableVariables() foreach (var module in AssociatedModules) { - var clipVariables = chatBoxManager.Variables[module].Values.Select(clipVariable => clipVariable.Copy()).ToList(); + var clipVariables = chatBoxManager.TemplateVariables[module].Values.ToList(); AvailableVariables.AddRange(clipVariables); } } @@ -195,7 +195,7 @@ private void addStatesOfAddedModules(NotifyCollectionChangedEventArgs e) { var statesPrevious = States.Select(clipState => clipState.Copy()).ToList(); - var states = chatBoxManager.States[moduleName]; + var states = chatBoxManager.TemplateStates[moduleName]; foreach (var (stateName, clipState) in states) { @@ -230,7 +230,7 @@ private void addEventsOfAddedModules(NotifyCollectionChangedEventArgs e) { foreach (string moduleName in e.NewItems!) { - if (!chatBoxManager.Events.TryGetValue(moduleName, out var events)) continue; + if (!chatBoxManager.TemplateEvents.TryGetValue(moduleName, out var events)) continue; foreach (var (_, clipEvent) in events) { diff --git a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index 53eb5a17..a6d5b462 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -12,12 +12,4 @@ public class ClipVariable public required string Lookup { get; init; } public required string Name { get; init; } public required string Format { get; init; } - - public ClipVariable Copy() => new() - { - Module = Module, - Lookup = Lookup, - Name = Name, - Format = Format - }; } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index d0b6a7a5..b4e2fdf5 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -132,7 +132,7 @@ protected override void OnDrag(DragEvent e) var deltaX = e.Delta.X / Parent.DrawWidth; cumulativeDrag += deltaX; - if (Math.Abs(cumulativeDrag) >= chatBoxManager.Resolution) + if (Math.Abs(cumulativeDrag) >= chatBoxManager.TimelineResolution) { var newStart = Clip.Start.Value + Math.Sign(cumulativeDrag); var newEnd = Clip.End.Value + Math.Sign(cumulativeDrag); @@ -154,8 +154,8 @@ protected override void OnDrag(DragEvent e) private void updateSizeAndPosition() { - Width = Clip.Length * chatBoxManager.Resolution; - X = Clip.Start.Value * chatBoxManager.Resolution; + Width = Clip.Length * chatBoxManager.TimelineResolution; + X = Clip.Start.Value * chatBoxManager.TimelineResolution; } private partial class ResizeDetector : Container @@ -218,7 +218,7 @@ protected override void OnDrag(DragEvent e) e.Target = Parent.Parent; CumulativeDrag += NormaliseFunc.Invoke(e.Delta.X); - if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) + if (Math.Abs(CumulativeDrag) >= chatBoxManager.TimelineResolution) { var newStart = Clip.Start.Value + Math.Sign(CumulativeDrag); @@ -259,7 +259,7 @@ protected override void OnDrag(DragEvent e) e.Target = Parent.Parent; CumulativeDrag += NormaliseFunc.Invoke(e.Delta.X); - if (Math.Abs(CumulativeDrag) >= chatBoxManager.Resolution) + if (Math.Abs(CumulativeDrag) >= chatBoxManager.TimelineResolution) { var newEnd = Clip.End.Value + Math.Sign(CumulativeDrag); diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 8328eeab..09695bbf 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -161,7 +161,7 @@ private void generateGrid() RelativeSizeAxes = Axes.Y, RelativePositionAxes = Axes.X, Width = 5, - X = (chatBoxManager.Resolution * i) + X = (chatBoxManager.TimelineResolution * i) }); } diff --git a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs index 35f13803..d23b6e48 100644 --- a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs +++ b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs @@ -31,6 +31,7 @@ protected override void CreateAttributes() protected override void OnModuleStart() { index = 0; + ChangeStateTo(ChatBoxTextState.Default); } protected override void OnModuleUpdate() diff --git a/VRCOSC.Modules/Clock/ClockModule.cs b/VRCOSC.Modules/Clock/ClockModule.cs index 675b3e90..8e17329f 100644 --- a/VRCOSC.Modules/Clock/ClockModule.cs +++ b/VRCOSC.Modules/Clock/ClockModule.cs @@ -38,6 +38,11 @@ protected override void CreateAttributes() CreateVariable(ClockVariable.Period, "AM/PM", "{period}"); } + protected override void OnModuleStart() + { + ChangeStateTo(ClockState.Default); + } + protected override void OnModuleUpdate() { time = timezoneToTime(GetSetting(ClockSetting.Timezone)); diff --git a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs index a873836c..c2653bb6 100644 --- a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs @@ -52,13 +52,19 @@ protected override void CreateAttributes() protected override void OnModuleStart() { - base.OnModuleStart(); hardwareStatsProvider = new HardwareStatsProvider(); + ChangeStateTo(HardwareStatsState.Unavailable); } protected override void OnModuleUpdate() { - if (hardwareStatsProvider is null || !hardwareStatsProvider.CanAcceptQueries) return; + if (hardwareStatsProvider is null || !hardwareStatsProvider.CanAcceptQueries) + { + ChangeStateTo(HardwareStatsState.Unavailable); + return; + } + + ChangeStateTo(HardwareStatsState.Available); hardwareStatsProvider.Update(); diff --git a/VRCOSC.Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Modules/Heartrate/HeartRateModule.cs index ab58254f..9d621e63 100644 --- a/VRCOSC.Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HeartRateModule.cs @@ -39,10 +39,9 @@ protected override void CreateAttributes() protected override void OnModuleStart() { - base.OnModuleStart(); attemptConnection(); - lastHeartrateTime = DateTimeOffset.Now - heartrate_timeout; + ChangeStateTo(HeartrateState.Default); } private void attemptConnection() diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index 23bdee5f..7e02e51f 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -57,8 +57,6 @@ protected override void CreateAttributes() protected override async void OnModuleStart() { - base.OnModuleStart(); - var result = await mediaProvider.Hook(); if (!result) @@ -67,6 +65,8 @@ protected override async void OnModuleStart() Log("Try restarting the modules\nIf this persists you will need to restart your PC as Windows has not initialised media correctly"); } + ChangeStateTo(mediaProvider.State.IsPlaying ? MediaState.Playing : MediaState.Paused); + startProcesses(); } @@ -204,8 +204,6 @@ protected override void OnIntParameterReceived(Enum key, int value) private enum MediaSetting { - PausedBehaviour, - PausedText, StartList } diff --git a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs index 41e44fa2..1b5aa045 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs @@ -45,6 +45,11 @@ protected override void CreateAttributes() CreateVariable(OpenVrVariable.RightControllerBattery, @"Right Controller Battery", @"{rightcontrollerbattery}"); } + protected override void OnModuleStart() + { + ChangeStateTo(OpenVrState.Default); + } + protected override void OnModuleUpdate() { if (OVRClient.HasInitialised) diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index 637b8ee8..51e07f0a 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -33,19 +33,22 @@ protected override void CreateAttributes() protected override void OnModuleStart() { - base.OnModuleStart(); - if (string.IsNullOrEmpty(GetSetting(WeatherSetting.Postcode))) Log("Please provide a postcode/zip code or city name"); weatherProvider = new WeatherProvider(Secrets.GetSecret(VRCOSCSecretsKeys.Weather)); currentWeather = null; + ChangeStateTo(WeatherState.Default); } protected override void OnModuleUpdate() { if (string.IsNullOrEmpty(GetSetting(WeatherSetting.Postcode))) return; - if (weatherProvider is null) return; + if (weatherProvider is null) + { + Log("Unable to connect to weather service"); + return; + } Task.Run(async () => { @@ -66,7 +69,12 @@ protected override void OnAvatarChange() private void updateParameters() { - if (currentWeather is null) return; + if (currentWeather is null) + { + Log("Cannot retrieve weather for provided location"); + Log("If you've entered a post/zip code, try your closest city's name"); + return; + } SendParameter(WeatherParameter.Code, convertedWeatherCode); From ceab86ce24ae6275974180fe51440952be379c1d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 10 Apr 2023 22:18:46 +0100 Subject: [PATCH 35/59] `Partial` no longer needed on module classes --- VRCOSC.Modules/Discord/DiscordModule.cs | 2 +- VRCOSC.Modules/FaceTracking/SRanipalModule.cs | 2 +- VRCOSC.Modules/Heartrate/HypeRate/HypeRateModule.cs | 2 +- VRCOSC.Modules/Heartrate/Pulsoid/PulsoidModule.cs | 2 +- VRCOSC.Modules/OpenVR/GestureExtensionsModule.cs | 2 +- VRCOSC.Modules/OpenVR/OpenVRControllerStatisticsModule.cs | 2 +- VRCOSC.Modules/Random/RandomBoolModule.cs | 2 +- VRCOSC.Modules/Random/RandomFloatModule.cs | 2 +- VRCOSC.Modules/Random/RandomIntModule.cs | 2 +- VRCOSC.Modules/Random/RandomModule.cs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/VRCOSC.Modules/Discord/DiscordModule.cs b/VRCOSC.Modules/Discord/DiscordModule.cs index 11b38f6c..783ebeba 100644 --- a/VRCOSC.Modules/Discord/DiscordModule.cs +++ b/VRCOSC.Modules/Discord/DiscordModule.cs @@ -6,7 +6,7 @@ namespace VRCOSC.Modules.Discord; -public sealed partial class DiscordModule : IntegrationModule +public sealed class DiscordModule : IntegrationModule { public override string Title => "Discord"; public override string Description => "Integration with the Discord desktop app"; diff --git a/VRCOSC.Modules/FaceTracking/SRanipalModule.cs b/VRCOSC.Modules/FaceTracking/SRanipalModule.cs index 13932f4b..d0aec349 100644 --- a/VRCOSC.Modules/FaceTracking/SRanipalModule.cs +++ b/VRCOSC.Modules/FaceTracking/SRanipalModule.cs @@ -10,7 +10,7 @@ namespace VRCOSC.Modules.FaceTracking; -public partial class SRanipalModule : Module +public class SRanipalModule : Module { public override string Title => "SRanipal"; public override string Description => "Hooks into SRanipal and sends face tracking data to VRChat. Interchangeable with VRCFaceTracking"; diff --git a/VRCOSC.Modules/Heartrate/HypeRate/HypeRateModule.cs b/VRCOSC.Modules/Heartrate/HypeRate/HypeRateModule.cs index 5c7b9c01..c6f1cd5c 100644 --- a/VRCOSC.Modules/Heartrate/HypeRate/HypeRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HypeRate/HypeRateModule.cs @@ -5,7 +5,7 @@ namespace VRCOSC.Modules.Heartrate.HypeRate; -public sealed partial class HypeRateModule : HeartRateModule +public sealed class HypeRateModule : HeartRateModule { public override string Title => "HypeRate"; public override string Description => "Connects to HypeRate.io and sends your heartrate to VRChat"; diff --git a/VRCOSC.Modules/Heartrate/Pulsoid/PulsoidModule.cs b/VRCOSC.Modules/Heartrate/Pulsoid/PulsoidModule.cs index d9e32257..40bc0720 100644 --- a/VRCOSC.Modules/Heartrate/Pulsoid/PulsoidModule.cs +++ b/VRCOSC.Modules/Heartrate/Pulsoid/PulsoidModule.cs @@ -3,7 +3,7 @@ namespace VRCOSC.Modules.Heartrate.Pulsoid; -public sealed partial class PulsoidModule : HeartRateModule +public sealed class PulsoidModule : HeartRateModule { private const string pulsoid_access_token_url = "https://pulsoid.net/oauth2/authorize?response_type=token&client_id=a31caa68-b6ac-4680-976a-9761b915a1e3&redirect_uri=&scope=data:heart_rate:read&state=a52beaeb-c491-4cd3-b915-16fed71e17a8&response_mode=web_page"; diff --git a/VRCOSC.Modules/OpenVR/GestureExtensionsModule.cs b/VRCOSC.Modules/OpenVR/GestureExtensionsModule.cs index ada0dfdd..e771e38d 100644 --- a/VRCOSC.Modules/OpenVR/GestureExtensionsModule.cs +++ b/VRCOSC.Modules/OpenVR/GestureExtensionsModule.cs @@ -7,7 +7,7 @@ namespace VRCOSC.Modules.OpenVR; -public partial class GestureExtensionsModule : Module +public class GestureExtensionsModule : Module { public override string Title => "Gesture Extensions"; public override string Description => "Detect a range of custom gestures from Index controllers"; diff --git a/VRCOSC.Modules/OpenVR/OpenVRControllerStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRControllerStatisticsModule.cs index 5010178c..7e47f7e5 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRControllerStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRControllerStatisticsModule.cs @@ -6,7 +6,7 @@ namespace VRCOSC.Modules.OpenVR; -public partial class OpenVRControllerStatisticsModule : Module +public class OpenVRControllerStatisticsModule : Module { public override string Title => "OpenVR Controller Statistics"; public override string Description => "Gets controller statistics from your OpenVR (SteamVR) session"; diff --git a/VRCOSC.Modules/Random/RandomBoolModule.cs b/VRCOSC.Modules/Random/RandomBoolModule.cs index e222f611..ed6a5987 100644 --- a/VRCOSC.Modules/Random/RandomBoolModule.cs +++ b/VRCOSC.Modules/Random/RandomBoolModule.cs @@ -3,7 +3,7 @@ namespace VRCOSC.Modules.Random; -public sealed partial class RandomBoolModule : RandomModule +public sealed class RandomBoolModule : RandomModule { protected override bool GetRandomValue() => RandomBool(); } diff --git a/VRCOSC.Modules/Random/RandomFloatModule.cs b/VRCOSC.Modules/Random/RandomFloatModule.cs index 35b7b0b4..ff804946 100644 --- a/VRCOSC.Modules/Random/RandomFloatModule.cs +++ b/VRCOSC.Modules/Random/RandomFloatModule.cs @@ -3,7 +3,7 @@ namespace VRCOSC.Modules.Random; -public sealed partial class RandomFloatModule : RandomModule +public sealed class RandomFloatModule : RandomModule { protected override void CreateAttributes() { diff --git a/VRCOSC.Modules/Random/RandomIntModule.cs b/VRCOSC.Modules/Random/RandomIntModule.cs index 8228d50c..abf38e35 100644 --- a/VRCOSC.Modules/Random/RandomIntModule.cs +++ b/VRCOSC.Modules/Random/RandomIntModule.cs @@ -3,7 +3,7 @@ namespace VRCOSC.Modules.Random; -public sealed partial class RandomIntModule : RandomModule +public sealed class RandomIntModule : RandomModule { protected override void CreateAttributes() { diff --git a/VRCOSC.Modules/Random/RandomModule.cs b/VRCOSC.Modules/Random/RandomModule.cs index dedac681..9493117f 100644 --- a/VRCOSC.Modules/Random/RandomModule.cs +++ b/VRCOSC.Modules/Random/RandomModule.cs @@ -6,7 +6,7 @@ namespace VRCOSC.Modules.Random; -public abstract partial class RandomModule : Module where T : struct +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"; From d365fb4340c03d1a5a119e275fc801d402749eda Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 16 Apr 2023 16:07:22 +0100 Subject: [PATCH 36/59] Add GUI and backend for states, events, and variables --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 63 +++---- VRCOSC.Game/ChatBox/Clips/Clip.cs | 44 ++--- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 33 +++- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 33 +++- VRCOSC.Game/ChatBox/Clips/ClipVariable.cs | 7 +- .../ChatBox/SelectedClip/DrawableEvent.cs | 128 +++++++++++++ .../SelectedClip/DrawableModuleVariables.cs | 59 ++++++ .../ChatBox/SelectedClip/DrawableState.cs | 122 ++++++++++++ .../ChatBox/SelectedClip/DrawableVariable.cs | 82 ++++++++ .../SelectedClip/SelectedClipEditorWrapper.cs | 2 +- .../SelectedClipModuleSelector.cs | 2 +- .../SelectedClipStateEditorContainer.cs | 176 ++++++++++++++++++ ...r.cs => SelectedClipStateEditorWrapper.cs} | 52 +++--- .../SelectedClipVariableContainer.cs | 121 ++++++++++++ .../Graphics/UI/VRCOSCScrollContainer.cs | 4 +- VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs | 5 + VRCOSC.Modules/AFK/AFKModule.cs | 4 +- .../ChatBoxText/ChatBoxTextModule.cs | 5 +- VRCOSC.Modules/Clock/ClockModule.cs | 10 +- .../HardwareStats/HardwareStatsModule.cs | 46 +++-- VRCOSC.Modules/Heartrate/HeartRateModule.cs | 4 +- VRCOSC.Modules/Media/MediaModule.cs | 16 +- .../OpenVR/OpenVRStatisticsModule.cs | 10 +- VRCOSC.Modules/Weather/WeatherModule.cs | 8 +- 24 files changed, 891 insertions(+), 145 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs rename VRCOSC.Game/Graphics/ChatBox/SelectedClip/{SelectedClipStateEditor.cs => SelectedClipStateEditorWrapper.cs} (56%) create mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 3987d258..91ad1404 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -28,16 +28,13 @@ public bool SendEnabled public readonly BindableList Clips = new(); - // TODO Might be best to refactor all this into metadata vs data. The template stuff should be the metadata which gets referenced by all the clips. The stored data for each state and event gets stored in the clips - // Dictionary> - public readonly Dictionary> TemplateVariables = new(); - public readonly Dictionary> TemplateStates = new(); - public readonly Dictionary> TemplateEvents = new(); + public readonly Dictionary> VariableMetadata = new(); + public readonly Dictionary> StateMetadata = new(); + public readonly Dictionary> EventMetadata = new(); public IReadOnlyDictionary ModuleEnabledCache = null!; // TODO These still get stored here since they're shared between clips for evaluation, but could be abstracted behind some helper methods to make things cleaner - // Dictionary> - public readonly Dictionary> VariableValues = new(); + public readonly Dictionary<(string, string), string?> VariableValues = new(); // Dictionary public readonly Dictionary StateValues = new(); @@ -139,7 +136,7 @@ private void setPriority(Clip clip, int priority) public void RegisterVariable(string module, string lookup, string name, string format) { - var variable = new ClipVariable + var variableMetadata = new ClipVariableMetadata { Module = module, Lookup = lookup, @@ -147,41 +144,36 @@ public void RegisterVariable(string module, string lookup, string name, string f Format = format }; - if (TemplateVariables.TryGetValue(module, out var innerDict)) + if (!VariableMetadata.ContainsKey(module)) { - innerDict.Add(lookup, variable); - } - else - { - TemplateVariables.Add(module, new Dictionary()); - TemplateVariables[module].Add(lookup, variable); + VariableMetadata.Add(module, new Dictionary()); } + + VariableMetadata[module][lookup] = variableMetadata; } public void SetVariable(string module, string lookup, string? value) { - if (!VariableValues.ContainsKey(module)) VariableValues.Add(module, new Dictionary()); - VariableValues[module][lookup] = value; + VariableValues[(module, lookup)] = value; } public void RegisterState(string module, string lookup, string name, string defaultFormat) { - var state = new ClipState + var stateMetadata = new ClipStateMetadata { - States = new List<(string, string)> { (module, lookup) }, - Format = { Value = defaultFormat } + Module = module, + Lookup = lookup, + Name = name, + DefaultFormat = defaultFormat }; - if (TemplateStates.TryGetValue(module, out var innerDict)) + if (!StateMetadata.ContainsKey(module)) { - innerDict.Add(lookup, state); - } - else - { - TemplateStates.Add(module, new Dictionary()); - TemplateStates[module].Add(lookup, state); + StateMetadata.Add(module, new Dictionary()); } + StateMetadata[module][lookup] = stateMetadata; + if (!StateValues.TryAdd(module, lookup)) { StateValues[module] = lookup; @@ -195,24 +187,21 @@ public void ChangeStateTo(string module, string lookup) public void RegisterEvent(string module, string lookup, string name, string defaultFormat, int defaultLength) { - var clipEvent = new ClipEvent + var eventMetadata = new ClipEventMetadata { Module = module, Lookup = lookup, Name = name, - Format = { Value = defaultFormat }, - Length = { Value = defaultLength } + DefaultFormat = defaultFormat, + DefaultLength = defaultLength }; - if (TemplateEvents.TryGetValue(module, out var innerDict)) + if (!EventMetadata.ContainsKey(module)) { - innerDict.Add(lookup, clipEvent); - } - else - { - TemplateEvents.Add(module, new Dictionary()); - TemplateEvents[module].Add(lookup, clipEvent); + EventMetadata.Add(module, new Dictionary()); } + + EventMetadata[module][lookup] = eventMetadata; } public void TriggerEvent(string module, string lookup) diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index c1956392..653b020a 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -20,7 +20,7 @@ public class Clip public readonly Bindable Name = new("New Clip"); public readonly BindableNumber Priority = new(); public readonly BindableList AssociatedModules = new(); - public readonly BindableList AvailableVariables = new(); + public readonly BindableList AvailableVariables = new(); public readonly BindableList States = new(); public readonly BindableList Events = new(); public readonly Bindable Start = new(); @@ -150,8 +150,8 @@ private string formatText(string text) AvailableVariables.ForEach(clipVariable => { - var variableValue = chatBoxManager.VariableValues[clipVariable.Module][clipVariable.Lookup] ?? string.Empty; - returnText = returnText.Replace(clipVariable.Format, variableValue); + var variableValue = chatBoxManager.VariableValues[(clipVariable.Module, clipVariable.Lookup)] ?? string.Empty; + returnText = returnText.Replace(clipVariable.DisplayableFormat, variableValue); }); return returnText; @@ -170,8 +170,7 @@ private void populateAvailableVariables() foreach (var module in AssociatedModules) { - var clipVariables = chatBoxManager.TemplateVariables[module].Values.ToList(); - AvailableVariables.AddRange(clipVariables); + AvailableVariables.AddRange(chatBoxManager.VariableMetadata[module].Values); } } @@ -191,24 +190,25 @@ private void removeStatesOfRemovedModules(NotifyCollectionChangedEventArgs e) private void addStatesOfAddedModules(NotifyCollectionChangedEventArgs e) { - foreach (string moduleName in e.NewItems!) - { - var statesPrevious = States.Select(clipState => clipState.Copy()).ToList(); + foreach (string moduleName in e.NewItems!) addStatesOfAddedModule(moduleName); + } + + private void addStatesOfAddedModule(string moduleName) + { + var currentStateCopy = States.Select(clipState => clipState.Copy()).ToList(); + var statesToAdd = chatBoxManager.StateMetadata[moduleName]; - var states = chatBoxManager.TemplateStates[moduleName]; + foreach (var (newStateName, newStateMetadata) in statesToAdd) + { + var localCurrentStatesCopy = currentStateCopy.Select(clipState => clipState.Copy()).ToList(); - foreach (var (stateName, clipState) in states) + localCurrentStatesCopy.ForEach(newStateLocal => { - var statesPreviousLocal = statesPrevious.Select(clipStateLocal => clipStateLocal.Copy()).ToList(); + newStateLocal.States.Add((moduleName, newStateName)); + }); - statesPreviousLocal.ForEach(localState => - { - localState.States.Add((moduleName, stateName)); - }); - - States.AddRange(statesPreviousLocal); - States.Add(clipState); - } + States.AddRange(localCurrentStatesCopy); + States.Add(new ClipState(newStateMetadata)); } } @@ -230,11 +230,11 @@ private void addEventsOfAddedModules(NotifyCollectionChangedEventArgs e) { foreach (string moduleName in e.NewItems!) { - if (!chatBoxManager.TemplateEvents.TryGetValue(moduleName, out var events)) continue; + if (!chatBoxManager.EventMetadata.TryGetValue(moduleName, out var events)) continue; - foreach (var (_, clipEvent) in events) + foreach (var (_, metadata) in events) { - Events.Add(clipEvent.Copy()); + Events.Add(new ClipEvent(metadata)); } } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index bcb46802..68e90b79 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -7,9 +7,9 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipEvent { - public required string Module { get; init; } - public required string Lookup { get; init; } - public required string Name { get; init; } + public string Module { get; init; } + public string Lookup { get; init; } + public string Name { get; init; } public Bindable Format = new(); public BindableBool Enabled = new(); public Bindable Length = new(); @@ -19,7 +19,30 @@ public class ClipEvent Module = Module, Lookup = Lookup, Name = Name, - Format = Format, - Length = Length + Format = new Bindable(Format.Value), + Length = new Bindable(Length.Value) }; + + public ClipEvent() + { + } + + public ClipEvent(ClipEventMetadata metadata) + { + Module = metadata.Module; + Lookup = metadata.Lookup; + Name = metadata.Name; + Format.Value = metadata.DefaultFormat; + Format.Default = metadata.DefaultFormat; + Length.Value = metadata.DefaultLength; + } +} + +public class ClipEventMetadata +{ + public required string Module { get; init; } + public required string Lookup { get; init; } + public required string Name { get; init; } + public required string DefaultFormat { get; init; } + public required int DefaultLength { get; init; } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index 472a73ec..59b26be1 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -9,15 +9,40 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { - public required List<(string, string)> States { get; init; } + public List<(string, string)> States { get; init; } public Bindable Format = new(); public BindableBool Enabled = new(); public List ModuleNames => States.Select(state => state.Item1).ToList(); public List StateNames => States.Select(state => state.Item2).ToList(); - public ClipState Copy() => new() + public ClipState Copy() { - States = States - }; + var statesCopy = new List<(string, string)>(); + States.ForEach(state => statesCopy.Add(state)); + + return new ClipState + { + States = statesCopy + }; + } + + public ClipState() + { + } + + public ClipState(ClipStateMetadata metadata) + { + States = new List<(string, string)> { (metadata.Module, metadata.Lookup) }; + Format.Value = metadata.DefaultFormat; + Format.Default = metadata.DefaultFormat; + } +} + +public class ClipStateMetadata +{ + public required string Module { get; init; } + public required string Lookup { get; init; } + public required string Name { get; init; } + public required string DefaultFormat { get; init; } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index a6d5b462..d0ab5b21 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -6,10 +6,15 @@ namespace VRCOSC.Game.ChatBox.Clips; /// /// Used by modules to denote a provided variable /// -public class ClipVariable +public class ClipVariableMetadata { + private const string variable_start_char = "{"; + private const string variable_end_char = "}"; + public required string Module { get; init; } public required string Lookup { get; init; } public required string Name { get; init; } public required string Format { get; init; } + + public string DisplayableFormat => $"{variable_start_char}{Module.ToLowerInvariant().Replace("module", string.Empty)}.{Format}{variable_end_char}"; } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs new file mode 100644 index 00000000..69e7f5e7 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs @@ -0,0 +1,128 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; +using VRCOSC.Game.Graphics.UI.Button; +using VRCOSC.Game.Graphics.UI.Text; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class DrawableEvent : Container +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + private readonly ClipEvent clipEvent; + + public DrawableEvent(ClipEvent clipEvent) + { + this.clipEvent = clipEvent; + } + + [BackgroundDependencyLoader] + private void load() + { + IntTextBox lengthTextBox; + + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.25f), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.15f) + }, + Content = new[] + { + new Drawable?[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Child = new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = clipEvent.Enabled + } + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = FrameworkFont.Regular.With(size: 20), + Text = clipEvent.Module.Replace("Module", string.Empty) + " - " + clipEvent.Name + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new VRCOSCTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Current = clipEvent.Format, + Masking = true, + CornerRadius = 5 + } + }, + null, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = lengthTextBox = new IntTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Text = clipEvent.Length.Value.ToString(), + Masking = true, + CornerRadius = 5 + } + }, + } + } + } + } + }; + + lengthTextBox.OnValidEntry = newLength => clipEvent.Length.Value = newLength; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs new file mode 100644 index 00000000..fb21efa0 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs @@ -0,0 +1,59 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osuTK; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class DrawableModuleVariables : Container +{ + private readonly string module; + private readonly List clipVariables; + + public DrawableModuleVariables(string module, List clipVariables) + { + this.module = module; + this.clipVariables = clipVariables; + } + + [BackgroundDependencyLoader] + private void load() + { + FillFlowContainer variableFlow; + + Child = variableFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + + variableFlow.Add(new SpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = module.Replace("Module", string.Empty), + Font = FrameworkFont.Regular.With(size: 20) + }); + + clipVariables.ForEach(clipVariable => + { + variableFlow.Add(new DrawableVariable(clipVariable) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + CornerRadius = 5 + }); + }); + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs new file mode 100644 index 00000000..f1ee050f --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -0,0 +1,122 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; +using VRCOSC.Game.Graphics.UI.Button; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class DrawableState : Container +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + private readonly ClipState state; + + public DrawableState(ClipState state) + { + this.state = state; + } + + [BackgroundDependencyLoader] + private void load() + { + var stateNameList = string.Empty; + + state.States.ForEach(pair => + { + var (moduleName, lookup) = pair; + var stateMetadata = chatBoxManager.StateMetadata[moduleName][lookup]; + stateNameList += moduleName.Replace("Module", string.Empty); + if (stateMetadata.Name != "Default") stateNameList += ":" + stateMetadata.Name; + stateNameList += " + "; + }); + + stateNameList = stateNameList.TrimEnd(' ', '+'); + + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.25f), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + }, + Content = new[] + { + new Drawable?[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Child = new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = state.Enabled + } + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = FrameworkFont.Regular.With(size: 20), + Text = stateNameList + } + } + }, + null, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new VRCOSCTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Current = state.Format, + Masking = true, + CornerRadius = 5 + } + } + } + } + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs new file mode 100644 index 00000000..b0c421eb --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs @@ -0,0 +1,82 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class DrawableVariable : Container +{ + private readonly ClipVariableMetadata clipVariable; + + public DrawableVariable(ClipVariableMetadata clipVariable) + { + this.clipVariable = clipVariable; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = clipVariable.Name + ":", + Font = FrameworkFont.Regular.With(size: 16) + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Mid], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Child = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = clipVariable.DisplayableFormat, + Font = FrameworkFont.Regular.With(size: 16) + } + } + } + } + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index 9fdf8c87..30920871 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -77,7 +77,7 @@ private void load() CornerRadius = 10 }, null, - new SelectedClipStateEditor + new SelectedClipStateEditorWrapper { RelativeSizeAxes = Axes.Both, Masking = true, diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index aa0d5286..618f1e8f 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -72,7 +72,7 @@ private void load() RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20) + Spacing = new Vector2(0, 10) } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs new file mode 100644 index 00000000..aaa01c33 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs @@ -0,0 +1,176 @@ +// 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.Allocation; +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 osuTK; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class SelectedClipStateEditorContainer : Container +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + private Clip clip; + private Container statesTitle; + private FillFlowContainer stateFlow; + private Container eventsTitle; + private FillFlowContainer eventFlow; + + public SelectedClipStateEditorContainer(Clip clip) + { + this.clip = clip; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Darker], + RelativeSizeAxes = Axes.Both + }, + new VRCOSCScrollContainer + { + RelativeSizeAxes = Axes.Both, + ClampExtension = 5, + ShowScrollbar = false, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + statesTitle = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Child = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "States", + Font = FrameworkFont.Regular.With(size: 30) + } + }, + stateFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Padding = new MarginPadding + { + Horizontal = 5, + Bottom = 5, + Top = 40 + } + } + } + }, + new LineSeparator(), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + eventsTitle = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Child = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Events", + Font = FrameworkFont.Regular.With(size: 30) + } + }, + eventFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Padding = new MarginPadding + { + Horizontal = 5, + Bottom = 5, + Top = 40 + } + } + } + } + } + } + } + }; + } + + protected override void LoadComplete() + { + clip.AssociatedModules.BindCollectionChanged((_, _) => + { + stateFlow.Clear(); + eventFlow.Clear(); + + clip.States.ForEach(clipState => + { + DrawableState drawableState; + + stateFlow.Add(drawableState = new DrawableState(clipState) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Masking = true, + CornerRadius = 5, + Height = 40 + }); + stateFlow.SetLayoutPosition(drawableState, clipState.States.Count); + }); + + clip.Events.ForEach(clipEvent => + { + eventFlow.Add(new DrawableEvent(clipEvent) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Masking = true, + CornerRadius = 5, + Height = 40 + }); + }); + + statesTitle.Alpha = stateFlow.Children.Count == 0 ? 0 : 1; + eventsTitle.Alpha = eventFlow.Children.Count == 0 ? 0 : 1; + }, true); + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs similarity index 56% rename from VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs rename to VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs index 60fcf1e9..0ac5ec5e 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs @@ -11,11 +11,14 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; -public partial class SelectedClipStateEditor : Container +public partial class SelectedClipStateEditorWrapper : Container { [Resolved] private Bindable selectedClip { get; set; } = null!; + private Container stateEditorContainer = null!; + private Container variableContainer = null!; + [BackgroundDependencyLoader] private void load() { @@ -43,39 +46,38 @@ private void load() { new Drawable?[] { - new Container + stateEditorContainer = new Container { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Darker], - RelativeSizeAxes = Axes.Both - }, - } + RelativeSizeAxes = Axes.Both }, null, - new Container + variableContainer = new Container { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Darker], - RelativeSizeAxes = Axes.Both - }, - } + RelativeSizeAxes = Axes.Both } } } } } }; + + selectedClip.BindValueChanged(clip => + { + if (clip.NewValue is null) return; + + stateEditorContainer.Child = new SelectedClipStateEditorContainer(clip.NewValue) + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5 + }; + + variableContainer.Child = new SelectedClipVariableContainer(clip.NewValue) + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5 + }; + }, true); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs new file mode 100644 index 00000000..67bd4234 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs @@ -0,0 +1,121 @@ +// 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.Allocation; +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 osuTK; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; + +namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; + +public partial class SelectedClipVariableContainer : Container +{ + private readonly Clip? clip; + private FillFlowContainer moduleVariableFlow = null!; + + public SelectedClipVariableContainer(Clip? clip) + { + this.clip = clip; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Darker], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.05f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Child = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Available Variables", + Font = FrameworkFont.Regular.With(size: 30) + } + } + }, + null, + new Drawable[] + { + new VRCOSCScrollContainer + { + RelativeSizeAxes = Axes.Both, + ShowScrollbar = false, + ClampExtension = 5, + Child = moduleVariableFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(5), + Spacing = new Vector2(0, 10) + } + } + } + } + } + } + }; + } + + protected override void LoadComplete() + { + clip?.AvailableVariables.BindCollectionChanged((_, _) => + { + moduleVariableFlow.Clear(); + + var groupedVariables = new Dictionary>(); + + clip.AvailableVariables.ForEach(clipVariable => + { + if (!groupedVariables.ContainsKey(clipVariable.Module)) groupedVariables.Add(clipVariable.Module, new List()); + groupedVariables[clipVariable.Module].Add(clipVariable); + }); + + groupedVariables.ForEach(pair => + { + var (moduleName, clipVariables) = pair; + + moduleVariableFlow.Add(new DrawableModuleVariables(moduleName, clipVariables) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); + }); + }, true); + } +} diff --git a/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs b/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs index b2f0e053..38950e33 100644 --- a/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs +++ b/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs @@ -20,6 +20,8 @@ public VRCOSCScrollContainer(Direction scrollDirection = Direction.Vertical) public partial class VRCOSCScrollContainer : ScrollContainer where T : Drawable { + public bool ShowScrollbar { get; init; } = true; + protected VRCOSCScrollContainer(Direction scrollDirection = Direction.Vertical) : base(scrollDirection) { @@ -30,7 +32,7 @@ protected override void UpdateAfterChildren() base.UpdateAfterChildren(); // we always want this to show - Scrollbar.Show(); + if (ShowScrollbar) Scrollbar.Show(); } protected override ScrollbarContainer CreateScrollbar(Direction direction) => new VRCOSCScrollbar(direction); diff --git a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs index 329e63af..c6d538ad 100644 --- a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs +++ b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs @@ -17,6 +17,11 @@ protected void SetVariableValue(Enum lookup, string? value) ChatBoxManager.SetVariable(SerialisedName, lookup.ToLookup(), value); } + protected string GetVariableFormat(Enum lookup) + { + return ChatBoxManager.VariableMetadata[SerialisedName][lookup.ToLookup()].DisplayableFormat; + } + protected void CreateState(Enum lookup, string name, string defaultFormat) { ChatBoxManager.RegisterState(SerialisedName, lookup.ToLookup(), name, defaultFormat); diff --git a/VRCOSC.Modules/AFK/AFKModule.cs b/VRCOSC.Modules/AFK/AFKModule.cs index 886683bb..6793acdf 100644 --- a/VRCOSC.Modules/AFK/AFKModule.cs +++ b/VRCOSC.Modules/AFK/AFKModule.cs @@ -17,9 +17,9 @@ public class AFKModule : ChatBoxModule protected override void CreateAttributes() { - CreateVariable(AFKModuleVariable.Duration, "Duration", "{duration}"); + CreateVariable(AFKModuleVariable.Duration, "Duration", "duration"); - CreateState(AFKModuleState.AFK, "AFK", "AFK for {duration}"); + CreateState(AFKModuleState.AFK, "AFK", $"AFK for {GetVariableFormat(AFKModuleVariable.Duration)}"); CreateState(AFKModuleState.NotAFK, "Not AFK", string.Empty); } diff --git a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs index d23b6e48..55b77bb4 100644 --- a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs +++ b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs @@ -24,8 +24,9 @@ protected override void CreateAttributes() CreateSetting(ChatBoxTextSetting.Splitter, "Splitter", "The splitter that goes between loops of the text", " | ", () => GetSetting(ChatBoxTextSetting.Animate)); CreateSetting(ChatBoxTextSetting.MaxLength, "Max Length", "The maximum length to show at one time when animating", 16, () => GetSetting(ChatBoxTextSetting.Animate)); - CreateState(ChatBoxTextState.Default, "Default", "{text}"); - CreateVariable(ChatBoxTextVariable.Text, "Text", "{text}"); + CreateVariable(ChatBoxTextVariable.Text, "Text", "text"); + + CreateState(ChatBoxTextState.Default, "Default", $"{GetVariableFormat(ChatBoxTextVariable.Text)}"); } protected override void OnModuleStart() diff --git a/VRCOSC.Modules/Clock/ClockModule.cs b/VRCOSC.Modules/Clock/ClockModule.cs index 8e17329f..be5a45f4 100644 --- a/VRCOSC.Modules/Clock/ClockModule.cs +++ b/VRCOSC.Modules/Clock/ClockModule.cs @@ -30,12 +30,12 @@ protected override void CreateAttributes() CreateParameter(ClockParameter.Minutes, ParameterMode.Write, "VRCOSC/Clock/Minutes", "Minutes", "The current minute normalised"); CreateParameter(ClockParameter.Seconds, ParameterMode.Write, "VRCOSC/Clock/Seconds", "Seconds", "The current second normalised"); - CreateState(ClockState.Default, "Default", "Local Time {h}:{m}{period}"); + CreateVariable(ClockVariable.Hours, "Hours", "h"); + CreateVariable(ClockVariable.Minutes, "Minutes", "m"); + CreateVariable(ClockVariable.Seconds, "Seconds", "s"); + CreateVariable(ClockVariable.Period, "AM/PM", "period"); - CreateVariable(ClockVariable.Hours, "Hours", "{h}"); - CreateVariable(ClockVariable.Minutes, "Minutes", "{m}"); - CreateVariable(ClockVariable.Seconds, "Seconds", "{s}"); - CreateVariable(ClockVariable.Period, "AM/PM", "{period}"); + CreateState(ClockState.Default, "Default", $"Local Time {GetVariableFormat(ClockVariable.Hours)}:{GetVariableFormat(ClockVariable.Minutes)}{GetVariableFormat(ClockVariable.Period)}"); } protected override void OnModuleStart() diff --git a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs index c2653bb6..d8310816 100644 --- a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs @@ -34,38 +34,45 @@ protected override void CreateAttributes() CreateParameter(HardwareStatsParameter.VRamUsed, ParameterMode.Write, "VRCOSC/Hardware/VRamUsed", @"VRAM Used", @"The amount of used VRAM in GB"); CreateParameter(HardwareStatsParameter.VRamTotal, ParameterMode.Write, "VRCOSC/Hardware/VRamTotal", @"VRAM Total", @"The amount of total VRAM in GB"); - CreateState(HardwareStatsState.Available, "Available", @"CPU: {cpuusage}% | GPU: {gpuusage}% RAM: {ramused}GB/{ramtotal}GB"); - CreateState(HardwareStatsState.Unavailable, "Unavailable", string.Empty); - - CreateVariable(HardwareStatsParameter.CpuUsage, @"CPU Usage (%)", @"{cpuusage}"); - CreateVariable(HardwareStatsParameter.GpuUsage, @"GPU Usage (%)", @"{gpuusage}"); - CreateVariable(HardwareStatsParameter.RamUsage, @"RAM Usage (%)", @"{ramusage}"); - CreateVariable(HardwareStatsParameter.CpuTemp, @"CPU Temp (C)", @"{cputemp}"); - CreateVariable(HardwareStatsParameter.GpuTemp, @"GPU Temp (C)", @"{gputemp}"); - CreateVariable(HardwareStatsParameter.RamTotal, @"RAM Total (GB)", @"{ramtotal}"); - CreateVariable(HardwareStatsParameter.RamUsed, @"RAM Used (GB)", @"{ramused}"); - CreateVariable(HardwareStatsParameter.RamAvailable, @"RAM Available (GB)", @"{ramavailable}"); - CreateVariable(HardwareStatsParameter.VRamUsed, @"VRAM Used (GB)", @"{vramused}"); - CreateVariable(HardwareStatsParameter.VRamFree, @"VRAM Free (GB)", @"{vramfree}"); - CreateVariable(HardwareStatsParameter.VRamTotal, @"VRAM Total (GB)", @"{vramtotal}"); + CreateVariable(HardwareStatsParameter.CpuUsage, @"CPU Usage (%)", @"cpuusage"); + CreateVariable(HardwareStatsParameter.GpuUsage, @"GPU Usage (%)", @"gpuusage"); + CreateVariable(HardwareStatsParameter.RamUsage, @"RAM Usage (%)", @"ramusage"); + CreateVariable(HardwareStatsParameter.CpuTemp, @"CPU Temp (C)", @"cputemp"); + CreateVariable(HardwareStatsParameter.GpuTemp, @"GPU Temp (C)", @"gputemp"); + CreateVariable(HardwareStatsParameter.RamTotal, @"RAM Total (GB)", @"ramtotal"); + CreateVariable(HardwareStatsParameter.RamUsed, @"RAM Used (GB)", @"ramused"); + CreateVariable(HardwareStatsParameter.RamAvailable, @"RAM Available (GB)", @"ramavailable"); + CreateVariable(HardwareStatsParameter.VRamUsed, @"VRAM Used (GB)", @"vramused"); + CreateVariable(HardwareStatsParameter.VRamFree, @"VRAM Free (GB)", @"vramfree"); + CreateVariable(HardwareStatsParameter.VRamTotal, @"VRAM Total (GB)", @"vramtotal"); + + CreateState(HardwareStatsState.Default, "Default", $"CPU: {GetVariableFormat(HardwareStatsParameter.CpuUsage)}% | GPU: {GetVariableFormat(HardwareStatsParameter.GpuUsage)}% RAM: {GetVariableFormat(HardwareStatsParameter.RamUsed)}GB/{GetVariableFormat(HardwareStatsParameter.RamTotal)}GB"); } protected override void OnModuleStart() { hardwareStatsProvider = new HardwareStatsProvider(); - ChangeStateTo(HardwareStatsState.Unavailable); + ChangeStateTo(HardwareStatsState.Default); } protected override void OnModuleUpdate() { if (hardwareStatsProvider is null || !hardwareStatsProvider.CanAcceptQueries) { - ChangeStateTo(HardwareStatsState.Unavailable); + SetVariableValue(HardwareStatsParameter.CpuUsage, "0"); + SetVariableValue(HardwareStatsParameter.GpuUsage, "0"); + SetVariableValue(HardwareStatsParameter.RamUsage, "0"); + SetVariableValue(HardwareStatsParameter.CpuTemp, "0"); + SetVariableValue(HardwareStatsParameter.GpuTemp, "0"); + SetVariableValue(HardwareStatsParameter.RamTotal, "0"); + SetVariableValue(HardwareStatsParameter.RamUsed, "0"); + SetVariableValue(HardwareStatsParameter.RamAvailable, "0"); + SetVariableValue(HardwareStatsParameter.VRamFree, "0"); + SetVariableValue(HardwareStatsParameter.VRamUsed, "0"); + SetVariableValue(HardwareStatsParameter.VRamTotal, "0"); return; } - ChangeStateTo(HardwareStatsState.Available); - hardwareStatsProvider.Update(); try @@ -129,7 +136,6 @@ private enum HardwareStatsParameter private enum HardwareStatsState { - Available, - Unavailable + Default } } diff --git a/VRCOSC.Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Modules/Heartrate/HeartRateModule.cs index 9d621e63..c7719735 100644 --- a/VRCOSC.Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HeartRateModule.cs @@ -32,9 +32,9 @@ protected override void CreateAttributes() CreateParameter(HeartrateParameter.Tens, ParameterMode.Write, "VRCOSC/Heartrate/Tens", "Tens", "The tens digit 0-9 mapped to a float"); CreateParameter(HeartrateParameter.Hundreds, ParameterMode.Write, "VRCOSC/Heartrate/Hundreds", "Hundreds", "The hundreds digit 0-9 mapped to a float"); - CreateState(HeartrateState.Default, @"Default", @"Heartrate {hr} bpm"); + CreateVariable(HeartrateVariable.Heartrate, @"Heartrate", @"hr"); - CreateVariable(HeartrateVariable.Heartrate, @"Heartrate", @"{hr}"); + CreateState(HeartrateState.Default, @"Default", $@"Heartrate {GetVariableFormat(HeartrateVariable.Heartrate)} bpm"); } protected override void OnModuleStart() diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index 7e02e51f..848eec30 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -43,16 +43,16 @@ protected override void CreateAttributes() CreateParameter(MediaParameter.Seeking, ParameterMode.Read, @"VRCOSC/Media/Seeking", "Seeking", "Whether the user is currently seeking"); CreateParameter(MediaParameter.Position, ParameterMode.ReadWrite, @"VRCOSC/Media/Position", "Position", "The position of the song as a percentage"); - CreateState(MediaState.Playing, "Playing", @"[{time}/{duration}] Now Playing: {artist} - {title}"); - CreateState(MediaState.Paused, "Paused", @"[Paused]"); + CreateVariable(MediaVariable.Title, @"Title", @"title"); + CreateVariable(MediaVariable.Artist, @"Artist", @"artist"); + CreateVariable(MediaVariable.Time, @"Time", @"time"); + CreateVariable(MediaVariable.Duration, @"Duration", @"duration"); + CreateVariable(MediaVariable.Volume, @"Volume", @"volume"); - CreateEvent(MediaEvent.NowPlaying, "Now Playing", @"[Now Playing] {artist} - {title}", 5); + CreateState(MediaState.Playing, "Playing", $@"[{GetVariableFormat(MediaVariable.Time)}/{GetVariableFormat(MediaVariable.Duration)}] Now Playing: {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}"); + CreateState(MediaState.Paused, "Paused", @"[Paused]"); - CreateVariable(MediaVariable.Title, @"Title", @"{title}"); - CreateVariable(MediaVariable.Artist, @"Artist", @"{artist}"); - CreateVariable(MediaVariable.Time, @"Time", @"{time}"); - CreateVariable(MediaVariable.Duration, @"Duration", @"{duration}"); - CreateVariable(MediaVariable.Volume, @"Volume", @"{volume}"); + CreateEvent(MediaEvent.NowPlaying, "Now Playing", $@"[Now Playing] {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}", 5); } protected override async void OnModuleStart() diff --git a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs index 1b5aa045..10ba83fa 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs @@ -37,12 +37,12 @@ protected override void CreateAttributes() CreateParameter(OpenVrParameter.Tracker1_Charging + i, ParameterMode.Write, $"VRCOSC/OpenVR/Trackers/{i + 1}/Charging", $"Tracker {i + 1} Charging", $"Whether tracker {i + 1} is currently charging"); } - CreateState(OpenVrState.Default, "Default", @"FPS: {fps} | HMD: {hmdbattery} | LC: {leftcontrollerbattery} | RC: {rightcontrollerbattery$}"); + CreateVariable(OpenVrVariable.FPS, @"FPS", @"fps"); + CreateVariable(OpenVrVariable.HMDBattery, @"HMD Battery", @"hmdbattery"); + CreateVariable(OpenVrVariable.LeftControllerBattery, @"Left Controller Battery", @"leftcontrollerbattery"); + CreateVariable(OpenVrVariable.RightControllerBattery, @"Right Controller Battery", @"rightcontrollerbattery"); - CreateVariable(OpenVrVariable.FPS, @"FPS", @"{fps}"); - CreateVariable(OpenVrVariable.HMDBattery, @"HMD Battery", @"{hmdbattery}"); - CreateVariable(OpenVrVariable.LeftControllerBattery, @"Left Controller Battery", @"{leftcontrollerbattery}"); - CreateVariable(OpenVrVariable.RightControllerBattery, @"Right Controller Battery", @"{rightcontrollerbattery}"); + CreateState(OpenVrState.Default, "Default", $@"FPS: {GetVariableFormat(OpenVrVariable.FPS)} | HMD: {GetVariableFormat(OpenVrVariable.HMDBattery)} | LC: {GetVariableFormat(OpenVrVariable.LeftControllerBattery)} | RC: {GetVariableFormat(OpenVrVariable.RightControllerBattery)}"); } protected override void OnModuleStart() diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index 51e07f0a..e577ab31 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -24,11 +24,11 @@ protected override void CreateAttributes() CreateParameter(WeatherParameter.Code, ParameterMode.Write, "VRCOSC/Weather/Code", "Weather Code", "The current weather's code"); - CreateState(WeatherState.Default, @"Default", @"Local Weather {tempc}C - {tempf}F"); + CreateVariable(WeatherVariable.TempC, @"Temp C", @"tempc"); + CreateVariable(WeatherVariable.TempF, @"Temp F", @"tempf"); + CreateVariable(WeatherVariable.Humidity, @"Humidity", @"humidity"); - CreateVariable(WeatherVariable.TempC, @"Temp C", @"{tempc}"); - CreateVariable(WeatherVariable.TempF, @"Temp F", @"{tempf}"); - CreateVariable(WeatherVariable.Humidity, @"Humidity", @"{humidity}"); + CreateState(WeatherState.Default, @"Default", $@"Local Weather {GetVariableFormat(WeatherVariable.TempC)}C - {GetVariableFormat(WeatherVariable.TempF)}F"); } protected override void OnModuleStart() From 92302954a0d9f454d8a4998e2c09d90077295f39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 20:16:34 +0000 Subject: [PATCH 37/59] Bump ppy.osu.Framework from 2023.228.0 to 2023.418.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2023.228.0 to 2023.418.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2023.228.0...2023.418.0) --- updated-dependencies: - dependency-name: ppy.osu.Framework dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index a69cf84d..a5d7ec83 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -18,7 +18,7 @@ - + all From 6bfba4859a90c94bd340b96b754f5cdc1f0ab40f Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Wed, 19 Apr 2023 17:15:56 +0100 Subject: [PATCH 38/59] Selected clip refactor --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 2 + VRCOSC.Game/ChatBox/Clips/Clip.cs | 28 ++----- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 5 -- .../ChatBox/Metadata/MetadataString.cs | 77 +++++++++++++++++++ .../ChatBox/Metadata/MetadataToggle.cs | 2 +- .../ChatBox/SelectedClip/DrawableEvent.cs | 3 +- .../ChatBox/SelectedClip/DrawableState.cs | 4 +- .../SelectedClip/SelectedClipEditorWrapper.cs | 6 +- .../SelectedClipMetadataEditor.cs | 19 +++-- .../SelectedClipModuleSelector.cs | 13 ++-- .../SelectedClipStateEditorWrapper.cs | 7 +- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 11 +-- .../ChatBox/Timeline/TimelineEditor.cs | 6 +- 13 files changed, 120 insertions(+), 63 deletions(-) create mode 100644 VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 91ad1404..8a73b66b 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -26,6 +26,8 @@ public bool SendEnabled } } + public readonly Bindable SelectedClip = new(); + public readonly BindableList Clips = new(); public readonly Dictionary> VariableMetadata = new(); diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 653b020a..e4cad22b 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -84,15 +84,11 @@ public bool Evalulate() private void removeDisabledModules(List localStates) { - var statesToRemove = new List(); - - foreach (ClipState clipState in localStates) + foreach (var clipState in localStates) { var stateValid = clipState.ModuleNames.All(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]); - if (!stateValid) statesToRemove.Add(clipState); + if (!stateValid) localStates.Remove(clipState); } - - statesToRemove.ForEach(moduleName => localStates.Remove(moduleName)); } private void removeLessCompoundedStates(List localStates) @@ -100,17 +96,13 @@ private void removeLessCompoundedStates(List localStates) var enabledAndAssociatedModules = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]).ToList(); enabledAndAssociatedModules.Sort(); - var statesToRemove = new List(); - - localStates.ForEach(clipState => + foreach (var clipState in localStates) { var clipStateModules = clipState.ModuleNames; clipStateModules.Sort(); - if (!clipStateModules.SequenceEqual(enabledAndAssociatedModules)) statesToRemove.Add(clipState); - }); - - statesToRemove.ForEach(clipState => localStates.Remove(clipState)); + if (!clipStateModules.SequenceEqual(enabledAndAssociatedModules)) localStates.Remove(clipState); + } } private void removeInvalidStates(List localStates) @@ -120,17 +112,13 @@ private void removeInvalidStates(List localStates) if (!currentStates.Any()) return; - var statesToRemove = new List(); - - localStates.ForEach(clipState => + foreach (var clipState in localStates) { var clipStateStates = clipState.StateNames; clipStateStates.Sort(); - if (!clipStateStates.SequenceEqual(currentStates)) statesToRemove.Add(clipState); - }); - - statesToRemove.ForEach(clipState => localStates.Remove(clipState)); + if (!clipStateStates.SequenceEqual(currentStates)) localStates.Remove(clipState); + } } public string GetFormattedText() diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index 3b327dfa..b4a281f6 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -2,11 +2,9 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.SelectedClip; using VRCOSC.Game.Graphics.ChatBox.Timeline; using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Clip; @@ -18,9 +16,6 @@ namespace VRCOSC.Game.Graphics.ChatBox; [Cached] public partial class ChatBoxScreen : Container { - [Cached] - private Bindable selectedClip { get; set; } = new(); - [Cached] private TimelineLayerMenu layerMenu = new(); diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs new file mode 100644 index 00000000..6d9c3e17 --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs @@ -0,0 +1,77 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.ChatBox; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI.Text; + +namespace VRCOSC.Game.Graphics.ChatBox.Metadata; + +public partial class MetadataString : Container +{ + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; + + public required string Label { get; init; } + public required Bindable Current { get; init; } + + private LocalTextBox inputTextBox = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 25), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = inputTextBox = new LocalTextBox + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + MinimumLength = 2, + } + } + }; + } + + protected override void LoadComplete() + { + inputTextBox.Text = Current.Value; + inputTextBox.OnValidEntry += value => Current.Value = value; + } + + private partial class LocalTextBox : StringTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; + } + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs index 1cef941a..b79ec1cb 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs @@ -47,7 +47,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = (BindableBool)State.GetBoundCopy() + State = State } } }; diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs index 69e7f5e7..5cd63c73 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs @@ -114,7 +114,8 @@ private void load() RelativeSizeAxes = Axes.Both, Text = clipEvent.Length.Value.ToString(), Masking = true, - CornerRadius = 5 + CornerRadius = 5, + PlaceholderText = "Length" } }, } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs index f1ee050f..80655086 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -60,12 +60,11 @@ private void load() ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.25f), - new Dimension(GridSizeMode.Absolute, 10), new Dimension() }, Content = new[] { - new Drawable?[] + new Drawable[] { new FillFlowContainer { @@ -99,7 +98,6 @@ private void load() } } }, - null, new Container { RelativeSizeAxes = Axes.Both, diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index 30920871..a4d203a7 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -2,11 +2,11 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; @@ -15,7 +15,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipEditorWrapper : Container { [Resolved] - private Bindable selectedClip { get; set; } = null!; + private ChatBoxManager chatBoxManager { get; set; } = null!; private Container noClipContent = null!; private GridContainer gridContent = null!; @@ -88,7 +88,7 @@ private void load() } }; - selectedClip.BindValueChanged(e => selectBestVisual(e.NewValue), true); + chatBoxManager.SelectedClip.BindValueChanged(e => selectBestVisual(e.NewValue), true); } private void selectBestVisual(Clip? clip) diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 6a4b1e2c..053dc68d 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -19,9 +18,6 @@ public partial class SelectedClipMetadataEditor : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - [Resolved] - private Bindable selectedClip { get; set; } = null!; - private FillFlowContainer metadataFlow = null!; [BackgroundDependencyLoader] @@ -44,22 +40,31 @@ private void load() Spacing = new Vector2(0, 5) } }; + } - selectedClip.BindValueChanged(e => onSelectedClipChange(e.NewValue), true); + protected override void LoadComplete() + { + chatBoxManager.SelectedClip.BindValueChanged(e => onSelectedClipChange(e.NewValue), true); } private void onSelectedClipChange(Clip? clip) { - metadataFlow.Clear(); - if (clip is null) return; + metadataFlow.Clear(); + metadataFlow.Add(new MetadataToggle { Label = "Enabled", State = clip.Enabled }); + metadataFlow.Add(new MetadataString + { + Label = "Name", + Current = clip.Name + }); + metadataFlow.Add(new ReadonlyTimeDisplay { Label = "Start", diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index 618f1e8f..d9640f3c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -3,12 +3,11 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; -using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Modules; @@ -19,7 +18,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipModuleSelector : Container { [Resolved] - private Bindable selectedClip { get; set; } = null!; + private ChatBoxManager chatBoxManager { get; set; } = null!; [Resolved] private GameManager gameManager { get; set; } = null!; @@ -81,10 +80,12 @@ private void load() } }; - selectedClip.BindValueChanged(e => + chatBoxManager.SelectedClip.BindValueChanged(e => { if (e.NewValue is null) return; + var newClip = e.NewValue; + moduleFlow.Clear(); foreach (var module in gameManager.ModuleManager.Where(module => module.GetType().IsSubclassOf(typeof(ChatBoxModule)))) @@ -107,9 +108,9 @@ private void load() drawableAssociatedModule.State.BindValueChanged(e => { if (e.NewValue) - selectedClip.Value!.AssociatedModules.Add(module.SerialisedName); + newClip.AssociatedModules.Add(module.SerialisedName); else - selectedClip.Value!.AssociatedModules.Remove(module.SerialisedName); + newClip.AssociatedModules.Remove(module.SerialisedName); }); } }, true); diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs index 0ac5ec5e..3233d55c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs @@ -2,11 +2,10 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -14,7 +13,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipStateEditorWrapper : Container { [Resolved] - private Bindable selectedClip { get; set; } = null!; + private ChatBoxManager chatBoxManager { get; set; } = null!; private Container stateEditorContainer = null!; private Container variableContainer = null!; @@ -61,7 +60,7 @@ private void load() } }; - selectedClip.BindValueChanged(clip => + chatBoxManager.SelectedClip.BindValueChanged(clip => { if (clip.NewValue is null) return; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index b4e2fdf5..4974c9d6 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,9 +20,6 @@ namespace VRCOSC.Game.Graphics.ChatBox.Timeline; [Cached] public partial class DrawableClip : Container { - [Resolved] - private Bindable selectedClip { get; set; } = null!; - [Resolved] private TimelineEditor timelineEditor { get; set; } = null!; @@ -94,13 +90,12 @@ private void load() updateSizeAndPosition(); - selectedClip.BindValueChanged(e => + chatBoxManager.SelectedClip.BindValueChanged(e => { ((Container)Child).BorderThickness = Clip == e.NewValue ? 4 : 2; }, true); Clip.Name.BindValueChanged(e => drawName.Text = e.NewValue, true); - Clip.Enabled.BindValueChanged(e => Child.FadeTo(e.NewValue ? 1 : 0.5f), true); } @@ -109,7 +104,7 @@ protected override bool OnMouseDown(MouseDownEvent e) if (e.Button == MouseButton.Left) { timelineEditor.HideClipMenu(); - selectedClip.Value = Clip; + chatBoxManager.SelectedClip.Value = Clip; } else if (e.Button == MouseButton.Right) { @@ -125,7 +120,7 @@ protected override void OnDrag(DragEvent e) { base.OnDrag(e); - selectedClip.Value = Clip; + chatBoxManager.SelectedClip.Value = Clip; e.Target = Parent; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 09695bbf..bda7e58b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -22,9 +21,6 @@ namespace VRCOSC.Game.Graphics.ChatBox.Timeline; [Cached] public partial class TimelineEditor : Container { - [Resolved] - private Bindable selectedClip { get; set; } = null!; - [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; @@ -220,7 +216,7 @@ protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Left) { - selectedClip.Value = null; + chatBoxManager.SelectedClip.Value = null; clipMenu.Hide(); layerMenu.Hide(); } From adf814471b76388594618072036a40ee7ececb4b Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 20 Apr 2023 23:07:00 +0100 Subject: [PATCH 39/59] UI update + Fix functionalities --- VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 41 +-- .../ChatBox/Metadata/MetadataString.cs | 63 +++-- .../Graphics/ChatBox/Metadata/MetadataTime.cs | 78 ------ .../ChatBox/Metadata/MetadataToggle.cs | 64 +++-- .../ChatBox/Metadata/ReadonlyTimeDisplay.cs | 63 +++-- .../SelectedClip/DrawableAssociatedModule.cs | 54 ++-- .../SelectedClip/DrawableModuleVariables.cs | 10 +- .../ChatBox/SelectedClip/DrawableState.cs | 98 ++++---- .../ChatBox/SelectedClip/DrawableVariable.cs | 69 ++--- .../SelectedClip/SelectedClipEditorWrapper.cs | 92 +++---- .../SelectedClipMetadataEditor.cs | 54 +++- .../SelectedClipModuleSelector.cs | 31 ++- .../SelectedClipStateEditorContainer.cs | 237 ++++++++++-------- .../SelectedClipStateEditorWrapper.cs | 82 ------ .../SelectedClipVariableContainer.cs | 92 +++---- .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 2 +- .../ChatBox/Timeline/TimelineEditor.cs | 14 +- .../ChatBox/Timeline/TimelineEditorWrapper.cs | 45 ---- .../ChatBox/Timeline/TimelineLayer.cs | 4 +- .../Timeline/TimelineMetadataEditor.cs | 57 ----- VRCOSC.Game/Graphics/LineSeparator.cs | 7 + VRCOSC.Game/Modules/Manager/IModuleManager.cs | 1 + VRCOSC.Game/Modules/Manager/ModuleManager.cs | 2 + VRCOSC.Game/VRCOSCGame.cs | 4 +- VRCOSC.Modules/AFK/AFKModule.cs | 5 +- 25 files changed, 590 insertions(+), 679 deletions(-) delete mode 100644 VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs delete mode 100644 VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs delete mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs delete mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index b4a281f6..aaeb1fff 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -38,28 +38,35 @@ private void load() { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10), - Children = new Drawable[] + Child = new GridContainer { - new SelectedClipEditorWrapper + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding - { - Bottom = 2.5f - } + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension() }, - new TimelineEditorWrapper + Content = new[] { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding + new Drawable[] + { + new SelectedClipEditorWrapper + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both + }, + }, + null, + new Drawable[] { - Top = 2.5f, + new TimelineEditor + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs index 6d9c3e17..70fe520a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; @@ -27,33 +28,53 @@ private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; Children = new Drawable[] { - new Container + new Box { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Child = new SpriteText - { - Font = FrameworkFont.Regular.With(size: 25), - Text = Label - } + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = inputTextBox = new LocalTextBox + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(3), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - MinimumLength = 2, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding(2), + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 22), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + inputTextBox = new LocalTextBox + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + MinimumLength = 2, + } + } + } } } }; @@ -70,8 +91,8 @@ private partial class LocalTextBox : StringTextBox [BackgroundDependencyLoader] private void load() { - BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; - BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Dark]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Dark]; } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs deleted file mode 100644 index 53f66106..00000000 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataTime.cs +++ /dev/null @@ -1,78 +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.Globalization; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using VRCOSC.Game.ChatBox; -using VRCOSC.Game.Graphics.Themes; -using VRCOSC.Game.Graphics.UI.Text; - -namespace VRCOSC.Game.Graphics.ChatBox.Metadata; - -public partial class MetadataTime : Container -{ - [Resolved] - private ChatBoxManager chatBoxManager { get; set; } = null!; - - public required string Label { get; init; } - public required Bindable Current { get; init; } - - private IntTextBox inputTextBox = null!; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Child = new SpriteText - { - Font = FrameworkFont.Regular.With(size: 25), - Text = Label - } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = inputTextBox = new LocalTextBox - { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5 - } - } - }; - } - - protected override void LoadComplete() - { - inputTextBox.Text = Current.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture); - inputTextBox.OnValidEntry += value => Current.Value = TimeSpan.FromSeconds(value); - } - - private partial class LocalTextBox : IntTextBox - { - [BackgroundDependencyLoader] - private void load() - { - BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; - BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; - } - } -} diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs index b79ec1cb..8b4dfc1a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs @@ -5,7 +5,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.ChatBox.Metadata; @@ -20,34 +22,56 @@ private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; Children = new Drawable[] { - new Container + new Box { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Child = new SpriteText - { - Font = FrameworkFont.Regular.With(size: 25), - Text = Label - } + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Child = new ToggleButton + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(3), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - State = State + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding(2), + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 22), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = State + } + } + } } } }; diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs index e73d8d48..5ad69485 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; @@ -28,33 +29,53 @@ private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; Children = new Drawable[] { - new Container + new Box { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Child = new SpriteText - { - Font = FrameworkFont.Regular.With(size: 25), - Text = Label - } + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = textBox = new LocalTextBox + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(3), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - ReadOnly = true + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding(2), + Child = new SpriteText + { + Font = FrameworkFont.Regular.With(size: 22), + Text = Label + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + textBox = new LocalTextBox + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + ReadOnly = true + } + } + } } } }; @@ -77,8 +98,8 @@ private partial class LocalTextBox : VRCOSCTextBox [BackgroundDependencyLoader] private void load() { - BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Darker]; - BackgroundFocused = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Mid]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Mid]; } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs index 8e292cb5..98496512 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs @@ -5,7 +5,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -19,41 +21,55 @@ public partial class DrawableAssociatedModule : Container private void load() { RelativeSizeAxes = Axes.X; - Height = 50; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; Children = new Drawable[] { - new FillFlowContainer + new Box { - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(10, 0), + Colour = ThemeManager.Current[ThemeAttribute.Light], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(3), Children = new Drawable[] { new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Child = new ToggleButton + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding(2), + Child = new SpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - State = State + Font = FrameworkFont.Regular.With(size: 22), + Text = ModuleName } }, new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - Child = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 20)) + FillMode = FillMode.Fit, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - TextAnchor = Anchor.CentreLeft, - Text = ModuleName + new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = State + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs index fb21efa0..f72762e4 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs @@ -45,15 +45,7 @@ private void load() clipVariables.ForEach(clipVariable => { - variableFlow.Add(new DrawableVariable(clipVariable) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - CornerRadius = 5 - }); + variableFlow.Add(new DrawableVariable(clipVariable)); }); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs index 80655086..0c13124a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -37,11 +37,12 @@ private void load() var (moduleName, lookup) = pair; var stateMetadata = chatBoxManager.StateMetadata[moduleName][lookup]; stateNameList += moduleName.Replace("Module", string.Empty); - if (stateMetadata.Name != "Default") stateNameList += ":" + stateMetadata.Name; - stateNameList += " + "; + if (stateMetadata.Name != "Default") stateNameList += " - " + stateMetadata.Name; + stateNameList += " & "; }); - stateNameList = stateNameList.TrimEnd(' ', '+'); + stateNameList = stateNameList.TrimEnd(' ', '&'); + stateNameList += ":"; Children = new Drawable[] { @@ -50,68 +51,67 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Light], RelativeSizeAxes = Axes.Both }, - new Container + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), - Child = new GridContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(3), + Spacing = new Vector2(0, 2), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Container { - new Dimension(GridSizeMode.Relative, 0.25f), - new Dimension() - }, - Content = new[] - { - new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 30, + Children = new Drawable[] { - new FillFlowContainer + new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + FillMode = FillMode.Fit, + Child = new ToggleButton { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Child = new ToggleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - State = state.Enabled - } - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = FrameworkFont.Regular.With(size: 20), - Text = stateNameList - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = state.Enabled } }, new Container { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Child = new VRCOSCTextBox + Padding = new MarginPadding(3), + Child = new SpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Current = state.Format, - Masking = true, - CornerRadius = 5 + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = FrameworkFont.Regular.With(size: 20), + Text = stateNameList } } } + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new VRCOSCTextBox + { + RelativeSizeAxes = Axes.X, + Height = 30, + Current = state.Format, + Masking = true, + CornerRadius = 5 + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs index b0c421eb..3102e8bc 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -23,6 +24,13 @@ public DrawableVariable(ClipVariableMetadata clipVariable) [BackgroundDependencyLoader] private void load() { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; + Children = new Drawable[] { new Box @@ -30,53 +38,50 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Light], RelativeSizeAxes = Axes.Both }, - new Container + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), + Padding = new MarginPadding(3), + Direction = FillDirection.Vertical, Children = new Drawable[] { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = clipVariable.Name + ":", - Font = FrameworkFont.Regular.With(size: 16) - }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Width = 0.5f, - Masking = true, - CornerRadius = 5, - Children = new Drawable[] + Padding = new MarginPadding(2), + Child = new SpriteText { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Mid], - RelativeSizeAxes = Axes.Both - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Child = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = clipVariable.DisplayableFormat, - Font = FrameworkFont.Regular.With(size: 16) - } - } + Font = FrameworkFont.Regular.With(size: 20), + Text = clipVariable.Name + ":" } + }, + new LocalTextBox + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + RelativeSizeAxes = Axes.X, + Height = 25, + CornerRadius = 5, + Text = clipVariable.DisplayableFormat, + ReadOnly = true } } } }; } + + private partial class LocalTextBox : VRCOSCTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Mid]; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Mid]; + } + } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index a4d203a7..8095e50c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -25,9 +25,8 @@ private void load() { Children = new Drawable[] { - noClipContent = new Container + new Container { - Alpha = 0, RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = 10, @@ -38,50 +37,55 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Dark], RelativeSizeAxes = Axes.Both }, - new SpriteText + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = FrameworkFont.Regular.With(size: 40), - Text = "Select a clip to edit" - } - } - }, - gridContent = new GridContainer - { - Alpha = 0, - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension() - }, - Content = new[] - { - new Drawable?[] - { - new SelectedClipMetadataEditor - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 - }, - null, - new SelectedClipModuleSelector - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 - }, - null, - new SelectedClipStateEditorWrapper + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 + noClipContent = new Container + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = FrameworkFont.Regular.With(size: 40), + Text = "Select a clip to edit" + } + } + }, + gridContent = new GridContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.15f), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.15f), + }, + Content = new[] + { + new Drawable?[] + { + new SelectedClipMetadataEditor(), + null, + new SelectedClipModuleSelector(), + null, + new SelectedClipStateEditorContainer(), + null, + new SelectedClipVariableContainer() + } + } + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 053dc68d..cc68113b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -5,11 +5,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -23,21 +25,61 @@ public partial class SelectedClipMetadataEditor : Container [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + Children = new Drawable[] { new Box { - Colour = ThemeManager.Current[ThemeAttribute.Dark], + Colour = ThemeManager.Current[ThemeAttribute.Darker], RelativeSizeAxes = Axes.Both }, - metadataFlow = new FillFlowContainer + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10), - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5) + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.05f), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Clip Settings", + Font = FrameworkFont.Regular.With(size: 30) + } + }, + null, + new Drawable[] + { + new VRCOSCScrollContainer + { + RelativeSizeAxes = Axes.Both, + ShowScrollbar = false, + ClampExtension = 5, + Child = metadataFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + } + } + } + } + } } }; } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index d9640f3c..c52c0095 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -6,10 +6,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; -using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Modules; using VRCOSC.Game.Modules.ChatBox; @@ -20,7 +20,6 @@ public partial class SelectedClipModuleSelector : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - [Resolved] private GameManager gameManager { get; set; } = null!; private FillFlowContainer moduleFlow = null!; @@ -28,11 +27,15 @@ public partial class SelectedClipModuleSelector : Container [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + Children = new Drawable[] { new Box { - Colour = ThemeManager.Current[ThemeAttribute.Dark], + Colour = ThemeManager.Current[ThemeAttribute.Darker], RelativeSizeAxes = Axes.Both }, new Container @@ -44,34 +47,36 @@ private void load() RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Relative, 0.15f), - new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Relative, 0.05f), + new Dimension(GridSizeMode.Absolute, 10), new Dimension() }, Content = new[] { new Drawable[] { - new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 30)) + new SpriteText { - RelativeSizeAxes = Axes.Both, - Text = "Select ChatBox-enabled\nModules", - TextAnchor = Anchor.TopCentre + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Select Modules", + Font = FrameworkFont.Regular.With(size: 30) } }, null, new Drawable[] { - new VRCOSCScrollContainer + new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - ClampExtension = 20, + ClampExtension = 5, + ScrollbarVisible = false, Child = moduleFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) + Spacing = new Vector2(0, 5) } } } @@ -97,7 +102,7 @@ private void load() ModuleName = module.Title }); - foreach (string moduleName in e.NewValue.AssociatedModules) + foreach (string moduleName in newClip.AssociatedModules) { if (module.SerialisedName == moduleName) { diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs index aaa01c33..441273cd 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.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.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -9,9 +10,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox; -using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; -using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -20,20 +19,19 @@ public partial class SelectedClipStateEditorContainer : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - private Clip clip; - private Container statesTitle; - private FillFlowContainer stateFlow; - private Container eventsTitle; - private FillFlowContainer eventFlow; - - public SelectedClipStateEditorContainer(Clip clip) - { - this.clip = clip; - } + private Container statesTitle = null!; + private FillFlowContainer stateFlow = null!; + private LineSeparator separator = null!; + private Container eventsTitle = null!; + private FillFlowContainer eventFlow = null!; [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + Children = new Drawable[] { new Box @@ -41,88 +39,95 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Darker], RelativeSizeAxes = Axes.Both }, - new VRCOSCScrollContainer + new Container { RelativeSizeAxes = Axes.Both, - ClampExtension = 5, - ShowScrollbar = false, - Child = new FillFlowContainer + Padding = new MarginPadding(10), + Child = new BasicScrollContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Spacing = new Vector2(0, 5), - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + ClampExtension = 5, + ScrollbarVisible = false, + Child = new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 10), + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new FillFlowContainer { - statesTitle = new Container + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Child = new SpriteText + statesTitle = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "States", - Font = FrameworkFont.Regular.With(size: 30) - } - }, - stateFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "States", + Font = FrameworkFont.Regular.With(size: 30) + } + }, + stateFlow = new FillFlowContainer { - Horizontal = 5, - Bottom = 5, - Top = 40 + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) } } - } - }, - new LineSeparator(), - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + }, + separator = new LineSeparator + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + LineColour = ThemeManager.Current[ThemeAttribute.Mid] + }, + new FillFlowContainer { - eventsTitle = new Container + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Child = new SpriteText + eventsTitle = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Events", - Font = FrameworkFont.Regular.With(size: 30) - } - }, - eventFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Events", + Font = FrameworkFont.Regular.With(size: 30) + } + }, + eventFlow = new FillFlowContainer { - Horizontal = 5, - Bottom = 5, - Top = 40 + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) } } } @@ -135,42 +140,60 @@ private void load() protected override void LoadComplete() { - clip.AssociatedModules.BindCollectionChanged((_, _) => + chatBoxManager.SelectedClip.BindValueChanged(e => { - stateFlow.Clear(); - eventFlow.Clear(); + if (e.OldValue is not null) e.OldValue.AssociatedModules.CollectionChanged -= associatedModulesOnCollectionChanged; - clip.States.ForEach(clipState => + if (e.NewValue is not null) { - DrawableState drawableState; + e.NewValue.AssociatedModules.CollectionChanged += associatedModulesOnCollectionChanged; + associatedModulesOnCollectionChanged(null, null); + } + }, true); + } - stateFlow.Add(drawableState = new DrawableState(clipState) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Masking = true, - CornerRadius = 5, - Height = 40 - }); - stateFlow.SetLayoutPosition(drawableState, clipState.States.Count); + private void associatedModulesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) + { + // TODO Add button to filter states/events of modules where the module is relevant (enabled) or show all states/events + gameManager.ModuleManager.GetEnabledModuleNames(); + + // Get module states of all associated modules, then filter states that contain all enabled associated modules + + stateFlow.Clear(); + eventFlow.Clear(); + + // TODO - Don't regenerate whole + + chatBoxManager.SelectedClip.Value?.States.ForEach(clipState => + { + DrawableState drawableState; + + stateFlow.Add(drawableState = new DrawableState(clipState) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + CornerRadius = 5, }); + stateFlow.SetLayoutPosition(drawableState, clipState.States.Count); + }); - clip.Events.ForEach(clipEvent => + chatBoxManager.SelectedClip.Value?.Events.ForEach(clipEvent => + { + eventFlow.Add(new DrawableEvent(clipEvent) { - eventFlow.Add(new DrawableEvent(clipEvent) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Masking = true, - CornerRadius = 5, - Height = 40 - }); + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Masking = true, + CornerRadius = 5, + Height = 40 }); + }); - statesTitle.Alpha = stateFlow.Children.Count == 0 ? 0 : 1; - eventsTitle.Alpha = eventFlow.Children.Count == 0 ? 0 : 1; - }, true); + statesTitle.Alpha = stateFlow.Children.Count == 0 ? 0 : 1; + eventsTitle.Alpha = separator.Alpha = eventFlow.Children.Count == 0 ? 0 : 1; } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs deleted file mode 100644 index 3233d55c..00000000 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorWrapper.cs +++ /dev/null @@ -1,82 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using VRCOSC.Game.ChatBox; -using VRCOSC.Game.Graphics.Themes; - -namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; - -public partial class SelectedClipStateEditorWrapper : Container -{ - [Resolved] - private ChatBoxManager chatBoxManager { get; set; } = null!; - - private Container stateEditorContainer = null!; - private Container variableContainer = null!; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Dark], - RelativeSizeAxes = Axes.Both - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.Relative, 0.25f), - }, - Content = new[] - { - new Drawable?[] - { - stateEditorContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - null, - variableContainer = new Container - { - RelativeSizeAxes = Axes.Both - } - } - } - } - } - }; - - chatBoxManager.SelectedClip.BindValueChanged(clip => - { - if (clip.NewValue is null) return; - - stateEditorContainer.Child = new SelectedClipStateEditorContainer(clip.NewValue) - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5 - }; - - variableContainer.Child = new SelectedClipVariableContainer(clip.NewValue) - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5 - }; - }, true); - } -} diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs index 67bd4234..de7dccb7 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the repository root for full license text. using System.Collections.Generic; +using System.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -9,25 +10,26 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; -using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class SelectedClipVariableContainer : Container { - private readonly Clip? clip; - private FillFlowContainer moduleVariableFlow = null!; + [Resolved] + private ChatBoxManager chatBoxManager { get; set; } = null!; - public SelectedClipVariableContainer(Clip? clip) - { - this.clip = clip; - } + private FillFlowContainer moduleVariableFlow = null!; [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + Children = new Drawable[] { new Box @@ -38,48 +40,41 @@ private void load() new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), + Padding = new MarginPadding(10), Child = new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.05f), - new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Absolute, 10), new Dimension() }, Content = new[] { new Drawable[] { - new Container + new SpriteText { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Child = new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Available Variables", - Font = FrameworkFont.Regular.With(size: 30) - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Available Variables", + Font = FrameworkFont.Regular.With(size: 30) } }, null, new Drawable[] { - new VRCOSCScrollContainer + new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - ShowScrollbar = false, + ScrollbarVisible = false, ClampExtension = 5, Child = moduleVariableFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Padding = new MarginPadding(5), Spacing = new Vector2(0, 10) } } @@ -92,30 +87,43 @@ private void load() protected override void LoadComplete() { - clip?.AvailableVariables.BindCollectionChanged((_, _) => + chatBoxManager.SelectedClip.BindValueChanged(e => { - moduleVariableFlow.Clear(); - - var groupedVariables = new Dictionary>(); + if (e.OldValue is not null) e.OldValue.AvailableVariables.CollectionChanged -= availableVariablesOnCollectionChanged; - clip.AvailableVariables.ForEach(clipVariable => + if (e.NewValue is not null) { - if (!groupedVariables.ContainsKey(clipVariable.Module)) groupedVariables.Add(clipVariable.Module, new List()); - groupedVariables[clipVariable.Module].Add(clipVariable); - }); + e.NewValue.AvailableVariables.CollectionChanged += availableVariablesOnCollectionChanged; + availableVariablesOnCollectionChanged(null, null); + } + }, true); + } - groupedVariables.ForEach(pair => - { - var (moduleName, clipVariables) = pair; + private void availableVariablesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) + { + moduleVariableFlow.Clear(); - moduleVariableFlow.Add(new DrawableModuleVariables(moduleName, clipVariables) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }); + // TODO Don't regenerate whole + + var groupedVariables = new Dictionary>(); + + chatBoxManager.SelectedClip.Value?.AvailableVariables.ForEach(clipVariable => + { + if (!groupedVariables.ContainsKey(clipVariable.Module)) groupedVariables.Add(clipVariable.Module, new List()); + groupedVariables[clipVariable.Module].Add(clipVariable); + }); + + groupedVariables.ForEach(pair => + { + var (moduleName, clipVariables) = pair; + + moduleVariableFlow.Add(new DrawableModuleVariables(moduleName, clipVariables) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y }); - }, true); + }); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index c0f28bc2..d6699b0c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -40,7 +40,7 @@ private void createClip() { var clip = chatBoxManager.CreateClip(); - var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false); + var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false, true); clip.Start.Value = lowerBound; clip.End.Value = upperBound; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index bda7e58b..9a402d88 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -41,6 +41,9 @@ public partial class TimelineEditor : Container [BackgroundDependencyLoader] private void load() { + Masking = true; + CornerRadius = 10; + Children = new Drawable[] { new Box @@ -56,22 +59,16 @@ private void load() new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(2), Child = new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), new Dimension(), }, Content = new[] @@ -83,7 +80,6 @@ private void load() Priority = 5 } }, - null, new Drawable[] { layer4 = new TimelineLayer @@ -91,7 +87,6 @@ private void load() Priority = 4 } }, - null, new Drawable[] { layer3 = new TimelineLayer @@ -99,7 +94,6 @@ private void load() Priority = 3 } }, - null, new Drawable[] { layer2 = new TimelineLayer @@ -107,7 +101,6 @@ private void load() Priority = 2 } }, - null, new Drawable[] { layer1 = new TimelineLayer @@ -115,7 +108,6 @@ private void load() Priority = 1 } }, - null, new Drawable[] { layer0 = new TimelineLayer diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs deleted file mode 100644 index c478a82b..00000000 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditorWrapper.cs +++ /dev/null @@ -1,45 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace VRCOSC.Game.Graphics.ChatBox.Timeline; - -public partial class TimelineEditorWrapper : Container -{ - [BackgroundDependencyLoader] - private void load() - { - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Relative, 0.175f), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(), - }, - Content = new[] - { - new Drawable?[] - { - new TimelineMetadataEditor - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 - }, - null, - new TimelineEditor - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10 - } - } - } - }; - } -} diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index 4ccbcac1..b71d73e3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -43,7 +43,7 @@ public void Remove(Clip clip) }); } - public (int, int) GetBoundsNearestTo(int value, bool end) + public (int, int) GetBoundsNearestTo(int value, bool end, bool isCreating = false) { var boundsList = new List(); @@ -68,7 +68,7 @@ public void Remove(Clip clip) boundsList.Sort(); var lowerBound = boundsList.Last(bound => bound <= value); - var upperBound = boundsList.First(bound => bound >= value); + var upperBound = boundsList.First(bound => bound >= value && (!isCreating || bound > lowerBound)); return (lowerBound, upperBound); } diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs deleted file mode 100644 index 156f03ab..00000000 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineMetadataEditor.cs +++ /dev/null @@ -1,57 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osuTK; -using VRCOSC.Game.ChatBox; -using VRCOSC.Game.Graphics.Themes; - -namespace VRCOSC.Game.Graphics.ChatBox.Timeline; - -public partial class TimelineMetadataEditor : Container -{ - [Resolved] - private ChatBoxManager chatBoxManager { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Box - { - Colour = ThemeManager.Current[ThemeAttribute.Dark], - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = FrameworkFont.Regular.With(size: 30), - Text = "Timeline" - }, - // new MetadataTime - // { - // Anchor = Anchor.TopCentre, - // Origin = Anchor.TopCentre, - // Label = "Length", - // Current = chatBoxManager.TimelineLength - // } - } - } - }; - } -} diff --git a/VRCOSC.Game/Graphics/LineSeparator.cs b/VRCOSC.Game/Graphics/LineSeparator.cs index 3e46ca1c..2254b463 100644 --- a/VRCOSC.Game/Graphics/LineSeparator.cs +++ b/VRCOSC.Game/Graphics/LineSeparator.cs @@ -11,11 +11,18 @@ namespace VRCOSC.Game.Graphics; public sealed partial class LineSeparator : CircularContainer { + public Colour4 LineColour + { + get => Child.Colour; + set => Child.Colour = value; + } + public LineSeparator() { RelativeSizeAxes = Axes.X; Size = new Vector2(0.95f, 5); Masking = true; + Child = new Box { Anchor = Anchor.Centre, diff --git a/VRCOSC.Game/Modules/Manager/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs index 3576dd7a..141f087f 100644 --- a/VRCOSC.Game/Modules/Manager/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -21,4 +21,5 @@ public interface IModuleManager : IEnumerable public void Start(); public void Update(); public void Stop(); + public IEnumerable GetEnabledModuleNames(); } diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index 8f6356e5..b49a424f 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -99,6 +99,8 @@ public void Stop() } } + public IEnumerable GetEnabledModuleNames() => modules.Where(module => module.Enabled.Value).Select(module => module.SerialisedName); + public IEnumerator GetEnumerator() => modules.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index 6eb2c68f..0394b55f 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -48,10 +48,10 @@ public abstract partial class VRCOSCGame : VRCOSCGameBase private Bindable typeFilter = new(); [Cached] - protected GameManager GameManager = new(); + public GameManager GameManager = new(); [Cached] - protected ChatBoxManager ChatBoxManager = new(); + public ChatBoxManager ChatBoxManager = new(); private NotificationContainer notificationContainer = null!; private VRCOSCUpdateManager updateManager = null!; diff --git a/VRCOSC.Modules/AFK/AFKModule.cs b/VRCOSC.Modules/AFK/AFKModule.cs index 6793acdf..c415fa91 100644 --- a/VRCOSC.Modules/AFK/AFKModule.cs +++ b/VRCOSC.Modules/AFK/AFKModule.cs @@ -18,6 +18,7 @@ public class AFKModule : ChatBoxModule protected override void CreateAttributes() { CreateVariable(AFKModuleVariable.Duration, "Duration", "duration"); + CreateVariable(AFKModuleVariable.Since, "Since", "since"); CreateState(AFKModuleState.AFK, "AFK", $"AFK for {GetVariableFormat(AFKModuleVariable.Duration)}"); CreateState(AFKModuleState.NotAFK, "Not AFK", string.Empty); @@ -49,12 +50,14 @@ protected override void OnModuleUpdate() } SetVariableValue(AFKModuleVariable.Duration, afkBegan is null ? null : (DateTime.Now - afkBegan.Value).ToString(@"hh\:mm\:ss")); + SetVariableValue(AFKModuleVariable.Since, afkBegan?.ToString(@"hh\:mm")); ChangeStateTo(afkBegan is null ? AFKModuleState.NotAFK : AFKModuleState.AFK); } private enum AFKModuleVariable { - Duration + Duration, + Since } private enum AFKModuleState From 0c2f10f38c46a9c85ca6bce834ad24cbce95820d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Fri, 21 Apr 2023 17:36:08 +0100 Subject: [PATCH 40/59] Allow tab switching when modules are running --- VRCOSC.Game/Graphics/TabBar/DrawableTab.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs b/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs index 68517fcc..ba26cec2 100644 --- a/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs +++ b/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs @@ -104,17 +104,6 @@ protected override void LoadComplete() Action += () => selectedTab.Value = Tab; } - protected override bool OnClick(ClickEvent e) - { - if (gameManager.State.Value is GameManagerState.Starting or GameManagerState.Started) - { - background.FlashColour(ThemeManager.Current[ThemeAttribute.Failure], 250, Easing.OutQuad); - return true; - } - - return base.OnClick(e); - } - protected override bool OnHover(HoverEvent e) { background.FadeColour(hover_colour, onhover_duration, Easing.InOutSine); From 73a7dc4160dbcc0e5fa6251e8fab9fb26bb83242 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 22 Apr 2023 14:50:12 +0100 Subject: [PATCH 41/59] UI changes + Bug fixes + Add remaining functionality --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 102 +++++++------ VRCOSC.Game/ChatBox/Clips/Clip.cs | 79 ++++++---- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 23 ++- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 26 +++- VRCOSC.Game/ChatBox/Clips/ClipVariable.cs | 2 +- VRCOSC.Game/ChatBox/DefaultTimeline.cs | 118 +++++++++++++++ VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs | 2 +- .../ChatBox/Metadata/MetadataString.cs | 4 +- .../ChatBox/Metadata/MetadataToggle.cs | 3 +- .../ChatBox/Metadata/ReadonlyTimeDisplay.cs | 3 +- .../SelectedClip/DrawableAssociatedModule.cs | 3 +- .../ChatBox/SelectedClip/DrawableEvent.cs | 136 ++++++++++-------- .../SelectedClip/DrawableModuleVariables.cs | 10 +- .../ChatBox/SelectedClip/DrawableState.cs | 22 +-- .../ChatBox/SelectedClip/DrawableVariable.cs | 3 +- .../SelectedClip/SelectedClipEditorWrapper.cs | 3 +- .../SelectedClipMetadataEditor.cs | 27 +++- .../SelectedClipModuleSelector.cs | 4 +- .../SelectedClipStateEditorContainer.cs | 12 +- .../SelectedClipVariableContainer.cs | 3 +- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 29 ++-- .../Timeline/Menu/Clip/TimelineClipMenu.cs | 23 ++- .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 4 +- .../ChatBox/Timeline/TimelineEditor.cs | 114 +++++---------- .../ChatBox/Timeline/TimelineLayer.cs | 7 +- .../ChatBox/Timeline/TimelineWrapper.cs | 40 ++++++ .../Attributes/Text/TextAttributeCard.cs | 2 +- .../Graphics/Settings/AutomationSection.cs | 2 +- .../Graphics/UI/Button/ToggleButton.cs | 1 + VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs | 39 ++--- VRCOSC.Game/Modules/GameManager.cs | 4 +- VRCOSC.Game/Modules/Manager/IModuleManager.cs | 1 + VRCOSC.Game/Modules/Manager/ModuleManager.cs | 2 + VRCOSC.Game/Modules/Module.cs | 5 +- .../ChatBoxText/ChatBoxTextModule.cs | 3 +- 35 files changed, 547 insertions(+), 314 deletions(-) create mode 100644 VRCOSC.Game/ChatBox/DefaultTimeline.cs create mode 100644 VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineWrapper.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 8a73b66b..9c3b4764 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.OSC.VRChat; namespace VRCOSC.Game.ChatBox; @@ -34,43 +35,36 @@ public bool SendEnabled public readonly Dictionary> StateMetadata = new(); public readonly Dictionary> EventMetadata = new(); public IReadOnlyDictionary ModuleEnabledCache = null!; + private Bindable sendDelay = null!; + private VRChatOscClient oscClient = null!; - // TODO These still get stored here since they're shared between clips for evaluation, but could be abstracted behind some helper methods to make things cleaner public readonly Dictionary<(string, string), string?> VariableValues = new(); - - // Dictionary public readonly Dictionary StateValues = new(); - - /// - /// The events occuring in the current update. Gets cleared after all Clips have been updated to ensure the event is only handled once - /// - // List public readonly List<(string, string)> TriggeredEvents = new(); + private readonly object triggeredEventsLock = new(); public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); public float TimelineResolution => 1f / (float)TimelineLength.Value.TotalSeconds; public int CurrentSecond => (int)Math.Floor((DateTimeOffset.Now - startTime).TotalSeconds) % (int)TimelineLength.Value.TotalSeconds; + private bool sendAllowed => nextValidTime <= DateTimeOffset.Now; private DateTimeOffset startTime; + private DateTimeOffset nextValidTime; private bool isClear; - public ChatBoxManager() + public void Load() { - for (var i = 0; i < priority_count; i++) - { - Clips.Add(new Clip(this) - { - Priority = { Value = i } - }); - } + // TODO Check for serialised file. If no file, generate default timeline + Clips.AddRange(DefaultTimeline.GenerateDefaultTimeline(this)); } - public Clip CreateClip() => new(this); - - public void Initialise(Dictionary moduleEnabledCache) + public void Initialise(VRChatOscClient oscClient, Bindable sendDelay, Dictionary moduleEnabledCache) { + this.oscClient = oscClient; + this.sendDelay = sendDelay; startTime = DateTimeOffset.Now; + nextValidTime = startTime; isClear = true; ModuleEnabledCache = moduleEnabledCache; @@ -79,63 +73,85 @@ public void Initialise(Dictionary moduleEnabledCache) public void Update() { - Clips.ForEach(clip => clip.Update()); - - Clip? validClip = null; - - for (var i = priority_count - 1; i >= 0; i--) + lock (triggeredEventsLock) { - Clips.Where(clip => clip.Priority.Value == i).ForEach(clip => - { - if (!clip.Evalulate() || validClip is not null) return; - - validClip = clip; - }); - - if (validClip is not null) break; + Clips.ForEach(clip => clip.Update()); + // Events get handled by clips in the same update cycle they're triggered + TriggeredEvents.Clear(); } - handleClip(validClip); - - TriggeredEvents.Clear(); + if (sendAllowed) evaluateClips(); } public void Shutdown() { - TriggeredEvents.Clear(); + lock (triggeredEventsLock) { TriggeredEvents.Clear(); } + VariableValues.Clear(); StateValues.Clear(); } + private void evaluateClips() + { + var validClip = getValidClip(); + handleClip(validClip); + nextValidTime += TimeSpan.FromMilliseconds(sendDelay.Value); + } + + private Clip? getValidClip() + { + for (var i = priority_count - 1; i >= 0; i--) + { + foreach (var clip in Clips.Where(clip => clip.Priority.Value == i)) + { + if (clip.Evalulate()) return clip; + } + } + + return null; + } + private void handleClip(Clip? clip) { - if (clip is null && !isClear) + if (clip is null) { - clearChatBox(); + if (!isClear) clearChatBox(); return; } - if (clip is null) return; + isClear = false; + sendText(clip.GetFormattedText()); + } - // format clip and send to ChatBox + private void sendText(string text) + { + oscClient.SendValues(VRChatOscConstants.ADDRESS_CHATBOX_INPUT, new List { text, true, false }); } private void clearChatBox() { + sendText(string.Empty); isClear = true; } + public void SetTyping(bool typing) + { + oscClient.SendValue(VRChatOscConstants.ADDRESS_CHATBOX_TYPING, typing); + } + public void IncreasePriority(Clip clip) => setPriority(clip, clip.Priority.Value + 1); public void DecreasePriority(Clip clip) => setPriority(clip, clip.Priority.Value - 1); private void setPriority(Clip clip, int priority) { if (priority is > priority_count - 1 or < 0) return; - if (RetrieveClipsWithPriority(priority).Any(clip.Intersects)) return; + if (Clips.Where(other => other.Priority.Value == priority).Any(clip.Intersects)) return; clip.Priority.Value = priority; } + public void DeleteClip(Clip clip) => Clips.Remove(clip); + public void RegisterVariable(string module, string lookup, string name, string format) { var variableMetadata = new ClipVariableMetadata @@ -208,8 +224,6 @@ public void RegisterEvent(string module, string lookup, string name, string defa public void TriggerEvent(string module, string lookup) { - TriggeredEvents.Add((module, lookup)); + lock (triggeredEventsLock) { TriggeredEvents.Add((module, lookup)); } } - - public IReadOnlyList RetrieveClipsWithPriority(int priority) => Clips.Where(clip => clip.Priority.Value == priority).ToList(); } diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index e4cad22b..2db4170e 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -1,10 +1,10 @@ -// 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.Collections.Generic; +using System.Collections.Immutable; using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -24,13 +24,13 @@ public class Clip public readonly BindableList States = new(); public readonly BindableList Events = new(); public readonly Bindable Start = new(); - public readonly Bindable End = new(30); + public readonly Bindable End = new(); public int Length => End.Value - Start.Value; private readonly ChatBoxManager chatBoxManager; - // TODO Dumb just store the ClipEvent somehow - private ((string, string), DateTimeOffset)? currentEvent; + private readonly Queue eventQueue = new(); + private (ClipEvent, DateTimeOffset)? currentEvent; private ClipState? currentState; public Clip(ChatBoxManager chatBoxManager) @@ -41,25 +41,54 @@ public Clip(ChatBoxManager chatBoxManager) public void Initialise() { + eventQueue.Clear(); currentEvent = null; currentState = null; } public void Update() + { + auditEvents(); + setCurrentEvent(); + } + + private void auditEvents() { chatBoxManager.TriggeredEvents.ForEach(moduleEvent => { var (module, lookup) = moduleEvent; - // TODO if new event's module name is equal to the current event's module name, it should replace - // TODO if new event's module name is different, add to a queue to be put into current event when current event expires + var clipEvents = Events.Where(clipEvent => clipEvent.Module == module && clipEvent.Lookup == lookup).ToList(); + if (!clipEvents.Any()) return; - var clipEvent = Events.Where(clipEvent => clipEvent.Module == module && clipEvent.Lookup == lookup); + var clipEvent = clipEvents.Single(); + if (!clipEvent.Enabled.Value) return; + + if (currentEvent?.Item1.Module == module) + // If the new event and current event are from the same module, overwrite the current event + currentEvent = (clipEvent, DateTimeOffset.Now + TimeSpan.FromSeconds(clipEvent.Length.Value)); + else + // If the new event and current event are from different modules, queue the new event + eventQueue.Enqueue(clipEvent); }); + } - if (currentEvent?.Item2 <= DateTimeOffset.Now) currentEvent = null; + private void setCurrentEvent() + { + if (currentEvent is not null && currentEvent.Value.Item2 < DateTimeOffset.Now) currentEvent = null; + + if (currentEvent is null && eventQueue.Any()) + { + var nextEvent = eventQueue.Dequeue(); + currentEvent = (nextEvent, DateTimeOffset.Now + TimeSpan.FromSeconds(nextEvent.Length.Value)); + } } + public ClipState GetStateFor(string module, string lookup) => getStateFor(new List { module }, new List { lookup }); + + private ClipState getStateFor(IReadOnlyCollection modules, IReadOnlyCollection lookups) => + States.Single(clipState => clipState.ModuleNames.SequenceEqual(modules) && clipState.StateNames.SequenceEqual(lookups)); + public bool Evalulate() { if (!Enabled.Value) return false; @@ -67,24 +96,23 @@ public bool Evalulate() if (currentEvent is not null) return true; - var localStates = States.Select(state => state.Copy()).ToList(); + var localStates = States.Select(state => state.Copy(true)).ToList(); removeDisabledModules(localStates); removeLessCompoundedStates(localStates); removeInvalidStates(localStates); - Debug.Assert(localStates.Count is 0 or 1); - if (!localStates.Any()) return false; - var chosenState = localStates.First(); + var chosenState = localStates.Single(); + if (!chosenState.Enabled.Value) return false; - currentState = chosenState.Enabled.Value ? chosenState : null; - return chosenState.Enabled.Value; + currentState = chosenState; + return true; } private void removeDisabledModules(List localStates) { - foreach (var clipState in localStates) + foreach (var clipState in localStates.ToImmutableList()) { var stateValid = clipState.ModuleNames.All(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]); if (!stateValid) localStates.Remove(clipState); @@ -96,7 +124,7 @@ private void removeLessCompoundedStates(List localStates) var enabledAndAssociatedModules = AssociatedModules.Where(moduleName => chatBoxManager.ModuleEnabledCache[moduleName]).ToList(); enabledAndAssociatedModules.Sort(); - foreach (var clipState in localStates) + foreach (var clipState in localStates.ToImmutableList()) { var clipStateModules = clipState.ModuleNames; clipStateModules.Sort(); @@ -112,7 +140,7 @@ private void removeInvalidStates(List localStates) if (!currentStates.Any()) return; - foreach (var clipState in localStates) + foreach (var clipState in localStates.ToImmutableList()) { var clipStateStates = clipState.StateNames; clipStateStates.Sort(); @@ -121,13 +149,7 @@ private void removeInvalidStates(List localStates) } } - public string GetFormattedText() - { - if (currentEvent is not null) - return formatText(Events.Single(clipEvent => clipEvent.Module == currentEvent.Value.Item1.Item1 && clipEvent.Lookup == currentEvent.Value.Item1.Item2)); - - return formatText(currentState!); - } + public string GetFormattedText() => currentEvent is not null ? formatText(currentEvent.Value.Item1) : formatText(currentState!); private string formatText(ClipState clipState) => formatText(clipState.Format.Value); private string formatText(ClipEvent clipEvent) => formatText(clipEvent.Format.Value); @@ -138,8 +160,11 @@ private string formatText(string text) AvailableVariables.ForEach(clipVariable => { - var variableValue = chatBoxManager.VariableValues[(clipVariable.Module, clipVariable.Lookup)] ?? string.Empty; - returnText = returnText.Replace(clipVariable.DisplayableFormat, variableValue); + if (!chatBoxManager.ModuleEnabledCache[clipVariable.Module]) return; + + chatBoxManager.VariableValues.TryGetValue((clipVariable.Module, clipVariable.Lookup), out var variableValue); + + returnText = returnText.Replace(clipVariable.DisplayableFormat, variableValue ?? string.Empty); }); return returnText; diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index 68e90b79..a858b360 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -7,25 +7,18 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipEvent { - public string Module { get; init; } - public string Lookup { get; init; } - public string Name { get; init; } - public Bindable Format = new(); - public BindableBool Enabled = new(); - public Bindable Length = new(); + public readonly string Module; + public readonly string Lookup; + public readonly string Name; - public ClipEvent Copy() => new() + public Bindable Format = new() { - Module = Module, - Lookup = Lookup, - Name = Name, - Format = new Bindable(Format.Value), - Length = new Bindable(Length.Value) + Default = string.Empty, + Value = string.Empty }; - public ClipEvent() - { - } + public BindableBool Enabled = new(); + public Bindable Length = new(); public ClipEvent(ClipEventMetadata metadata) { diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index 59b26be1..b214d17a 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -9,28 +9,40 @@ namespace VRCOSC.Game.ChatBox.Clips; public class ClipState { - public List<(string, string)> States { get; init; } - public Bindable Format = new(); + public List<(string, string)> States { get; private init; } = null!; + + public Bindable Format = new() + { + Default = string.Empty, + Value = string.Empty + }; + public BindableBool Enabled = new(); public List ModuleNames => States.Select(state => state.Item1).ToList(); public List StateNames => States.Select(state => state.Item2).ToList(); - public ClipState Copy() + public ClipState Copy(bool includeData = false) { var statesCopy = new List<(string, string)>(); States.ForEach(state => statesCopy.Add(state)); - return new ClipState + var copy = new ClipState { States = statesCopy }; - } - public ClipState() - { + if (includeData) + { + copy.Format = Format.GetUnboundCopy(); + copy.Enabled = (BindableBool)Enabled.GetUnboundCopy(); + } + + return copy; } + private ClipState() { } + public ClipState(ClipStateMetadata metadata) { States = new List<(string, string)> { (metadata.Module, metadata.Lookup) }; diff --git a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs index d0ab5b21..f0e6f71e 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipVariable.cs @@ -16,5 +16,5 @@ public class ClipVariableMetadata public required string Name { get; init; } public required string Format { get; init; } - public string DisplayableFormat => $"{variable_start_char}{Module.ToLowerInvariant().Replace("module", string.Empty)}.{Format}{variable_end_char}"; + public string DisplayableFormat => $"{variable_start_char}{Module.Replace("module", string.Empty)}.{Format}{variable_end_char}"; } diff --git a/VRCOSC.Game/ChatBox/DefaultTimeline.cs b/VRCOSC.Game/ChatBox/DefaultTimeline.cs new file mode 100644 index 00000000..fb3d0b86 --- /dev/null +++ b/VRCOSC.Game/ChatBox/DefaultTimeline.cs @@ -0,0 +1,118 @@ +// 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 VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox; + +public static class DefaultTimeline +{ + public static IEnumerable GenerateDefaultTimeline(ChatBoxManager chatBoxManager) + { + yield return generateClockClip(chatBoxManager); + yield return generateHwsClip(chatBoxManager); + yield return generateWeatherClip(chatBoxManager); + yield return generateHeartrateClip(chatBoxManager); + yield return generateChatBoxTextClip(chatBoxManager); + yield return generateMediaClip(chatBoxManager); + } + + private static Clip generateClockClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"Clock" }, + Priority = { Value = 0 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"clockmodule"); + clip.GetStateFor(@"clockmodule", @"default").Enabled.Value = true; + + return clip; + } + + private static Clip generateHwsClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"Hardware Stats" }, + Priority = { Value = 1 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"hardwarestatsmodule"); + clip.GetStateFor(@"hardwarestatsmodule", @"default").Enabled.Value = true; + + return clip; + } + + private static Clip generateWeatherClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"Weather" }, + Priority = { Value = 2 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"weathermodule"); + clip.GetStateFor(@"weathermodule", @"default").Enabled.Value = true; + + return clip; + } + + private static Clip generateHeartrateClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"Heartrate" }, + Priority = { Value = 3 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"hyperatemodule"); + clip.AssociatedModules.Add(@"pulsoidmodule"); + clip.GetStateFor(@"hyperatemodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"pulsoidmodule", @"default").Enabled.Value = true; + + return clip; + } + + private static Clip generateChatBoxTextClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"ChatBox Text" }, + Priority = { Value = 4 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"chatboxtextmodule"); + clip.GetStateFor(@"chatboxtextmodule", @"default").Enabled.Value = true; + + return clip; + } + + private static Clip generateMediaClip(ChatBoxManager chatBoxManager) + { + var clip = new Clip(chatBoxManager) + { + Name = { Value = @"Media" }, + Priority = { Value = 5 }, + Start = { Value = 0 }, + End = { Value = 60 } + }; + + clip.AssociatedModules.Add(@"mediamodule"); + clip.GetStateFor(@"mediamodule", @"playing").Enabled.Value = true; + + return clip; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs index aaeb1fff..909d3e78 100644 --- a/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs +++ b/VRCOSC.Game/Graphics/ChatBox/ChatBoxScreen.cs @@ -61,7 +61,7 @@ private void load() null, new Drawable[] { - new TimelineEditor + new TimelineWrapper { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs index 70fe520a..8a3832ad 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataString.cs @@ -56,7 +56,8 @@ private void load() Child = new SpriteText { Font = FrameworkFont.Regular.With(size: 22), - Text = Label + Text = Label, + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new Container @@ -72,6 +73,7 @@ private void load() RelativeSizeAxes = Axes.Both, CornerRadius = 5, MinimumLength = 2, + EmptyIsValid = false } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs index 8b4dfc1a..324d6e85 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs @@ -50,7 +50,8 @@ private void load() Child = new SpriteText { Font = FrameworkFont.Regular.With(size: 22), - Text = Label + Text = Label, + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new Container diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs index 5ad69485..697b1ec8 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/ReadonlyTimeDisplay.cs @@ -57,7 +57,8 @@ private void load() Child = new SpriteText { Font = FrameworkFont.Regular.With(size: 22), - Text = Label + Text = Label, + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new Container diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs index 98496512..3df14d69 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs @@ -50,7 +50,8 @@ private void load() Child = new SpriteText { Font = FrameworkFont.Regular.With(size: 22), - Text = ModuleName + Text = ModuleName, + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new Container diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs index 5cd63c73..815cce2b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs @@ -2,24 +2,25 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; -using VRCOSC.Game.ChatBox; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Graphics.UI.Text; +using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class DrawableEvent : Container { [Resolved] - private ChatBoxManager chatBoxManager { get; set; } = null!; + private GameManager gameManager { get; set; } = null!; private readonly ClipEvent clipEvent; @@ -40,84 +41,95 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Light], RelativeSizeAxes = Axes.Both }, - new Container + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), - Child = new GridContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(3), + Spacing = new Vector2(0, 2), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Container { - new Dimension(GridSizeMode.Relative, 0.25f), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.Relative, 0.15f) - }, - Content = new[] - { - new Drawable?[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 30, + Children = new Drawable[] { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Child = new ToggleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - State = clipEvent.Enabled - } - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = FrameworkFont.Regular.With(size: 20), - Text = clipEvent.Module.Replace("Module", string.Empty) + " - " + clipEvent.Name - } - } - }, new Container { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - Child = new VRCOSCTextBox + FillMode = FillMode.Fit, + Child = new ToggleButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Current = clipEvent.Format, - Masking = true, - CornerRadius = 5 + State = (BindableBool)clipEvent.Enabled.GetBoundCopy() } }, - null, new Container { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Child = lengthTextBox = new IntTextBox + Padding = new MarginPadding(3), + Child = new SpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Text = clipEvent.Length.Value.ToString(), - Masking = true, - CornerRadius = 5, - PlaceholderText = "Length" + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = FrameworkFont.Regular.With(size: 20), + Text = gameManager.ModuleManager.GetModuleName(clipEvent.Module) + " - " + clipEvent.Name + ":", + Colour = ThemeManager.Current[ThemeAttribute.Text] } - }, + } + } + }, + new GridContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 30, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 3), + new Dimension(GridSizeMode.Relative, 0.1f) + }, + Content = new[] + { + new Drawable?[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new VRCOSCTextBox + { + RelativeSizeAxes = Axes.Both, + Current = clipEvent.Format.GetBoundCopy(), + Masking = true, + CornerRadius = 5 + } + }, + null, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = lengthTextBox = new IntTextBox + { + RelativeSizeAxes = Axes.Both, + Text = clipEvent.Length.Value.ToString(), + Masking = true, + CornerRadius = 5, + PlaceholderText = "Length" + } + }, + } } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs index f72762e4..ca30b658 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableModuleVariables.cs @@ -8,11 +8,16 @@ using osu.Framework.Graphics.Sprites; using osuTK; using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class DrawableModuleVariables : Container { + [Resolved] + private GameManager gameManager { get; set; } = null!; + private readonly string module; private readonly List clipVariables; @@ -39,8 +44,9 @@ private void load() { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = module.Replace("Module", string.Empty), - Font = FrameworkFont.Regular.With(size: 20) + Text = gameManager.ModuleManager.GetModuleName(module), + Font = FrameworkFont.Regular.With(size: 20), + Colour = ThemeManager.Current[ThemeAttribute.Text] }); clipVariables.ForEach(clipVariable => diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs index 0c13124a..c933e07a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,6 +13,7 @@ using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Graphics.UI.Button; +using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -20,11 +22,14 @@ public partial class DrawableState : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - private readonly ClipState state; + [Resolved] + private GameManager gameManager { get; set; } = null!; + + private readonly ClipState clipState; - public DrawableState(ClipState state) + public DrawableState(ClipState clipState) { - this.state = state; + this.clipState = clipState; } [BackgroundDependencyLoader] @@ -32,11 +37,11 @@ private void load() { var stateNameList = string.Empty; - state.States.ForEach(pair => + clipState.States.ForEach(pair => { var (moduleName, lookup) = pair; var stateMetadata = chatBoxManager.StateMetadata[moduleName][lookup]; - stateNameList += moduleName.Replace("Module", string.Empty); + stateNameList += gameManager.ModuleManager.GetModuleName(moduleName); if (stateMetadata.Name != "Default") stateNameList += " - " + stateMetadata.Name; stateNameList += " & "; }); @@ -79,7 +84,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = state.Enabled + State = (BindableBool)clipState.Enabled.GetBoundCopy() } }, new Container @@ -93,7 +98,8 @@ private void load() Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = FrameworkFont.Regular.With(size: 20), - Text = stateNameList + Text = stateNameList, + Colour = ThemeManager.Current[ThemeAttribute.Text] } } } @@ -108,7 +114,7 @@ private void load() { RelativeSizeAxes = Axes.X, Height = 30, - Current = state.Format, + Current = clipState.Format.GetBoundCopy(), Masking = true, CornerRadius = 5 } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs index 3102e8bc..d09979aa 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableVariable.cs @@ -57,7 +57,8 @@ private void load() Child = new SpriteText { Font = FrameworkFont.Regular.With(size: 20), - Text = clipVariable.Name + ":" + Text = clipVariable.Name + ":", + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new LocalTextBox diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs index 8095e50c..6108b0f3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipEditorWrapper.cs @@ -54,7 +54,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = FrameworkFont.Regular.With(size: 40), - Text = "Select a clip to edit" + Text = "Select a clip to edit", + Colour = ThemeManager.Current[ThemeAttribute.Text] } } }, diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index cc68113b..79a7f139 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -58,7 +59,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Clip Settings", - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, null, @@ -98,25 +100,40 @@ private void onSelectedClipChange(Clip? clip) metadataFlow.Add(new MetadataToggle { Label = "Enabled", - State = clip.Enabled + State = (BindableBool)clip.Enabled.GetBoundCopy() }); metadataFlow.Add(new MetadataString { Label = "Name", - Current = clip.Name + Current = clip.Name.GetBoundCopy() + }); + + metadataFlow.Add(new SpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Metadata", + Font = FrameworkFont.Regular.With(size: 25), + Colour = ThemeManager.Current[ThemeAttribute.Text] }); metadataFlow.Add(new ReadonlyTimeDisplay { Label = "Start", - Current = clip.Start + Current = clip.Start.GetBoundCopy() }); metadataFlow.Add(new ReadonlyTimeDisplay { Label = "End", - Current = clip.End + Current = clip.End.GetBoundCopy() + }); + + metadataFlow.Add(new ReadonlyTimeDisplay + { + Label = "Priority", + Current = clip.Priority.GetBoundCopy() }); } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs index c52c0095..3d14cfe4 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipModuleSelector.cs @@ -20,6 +20,7 @@ public partial class SelectedClipModuleSelector : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] private GameManager gameManager { get; set; } = null!; private FillFlowContainer moduleFlow = null!; @@ -60,7 +61,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Select Modules", - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, null, diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs index 441273cd..26671115 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs @@ -76,7 +76,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "States", - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, stateFlow = new FillFlowContainer @@ -117,7 +118,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Events", - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, eventFlow = new FillFlowContainer @@ -155,7 +157,7 @@ protected override void LoadComplete() private void associatedModulesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) { // TODO Add button to filter states/events of modules where the module is relevant (enabled) or show all states/events - gameManager.ModuleManager.GetEnabledModuleNames(); + //gameManager.ModuleManager.GetEnabledModuleNames(); // Get module states of all associated modules, then filter states that contain all enabled associated modules @@ -187,9 +189,9 @@ private void associatedModulesOnCollectionChanged(object? sender, NotifyCollecti Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Masking = true, - CornerRadius = 5, - Height = 40 + CornerRadius = 5 }); }); diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs index de7dccb7..f0fa8f36 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipVariableContainer.cs @@ -59,7 +59,8 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Available Variables", - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, null, diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index 4974c9d6..ffd70f1f 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -32,6 +32,7 @@ public partial class DrawableClip : Container public readonly Clip Clip; private float cumulativeDrag; + private SpriteText drawName = null!; public DrawableClip(Clip clip) { @@ -44,14 +45,12 @@ private void load() RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.X; - SpriteText drawName; - Child = new Container { RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = 10, - BorderColour = ThemeManager.Current[ThemeAttribute.Lighter], + BorderColour = ThemeManager.Current[ThemeAttribute.Accent], Children = new Drawable[] { new Box @@ -62,14 +61,12 @@ private void load() new StartResizeDetector(Clip, v => v / Parent.DrawWidth) { RelativeSizeAxes = Axes.Y, - Width = 20 + Width = 15 }, new EndResizeDetector(Clip, v => v / Parent.DrawWidth) { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, - Width = 20 + Width = 15 }, new Container { @@ -81,15 +78,17 @@ private void load() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = FrameworkFont.Regular.With(size: 20) + Font = FrameworkFont.Regular.With(size: 20), + Colour = ThemeManager.Current[ThemeAttribute.Text] } } } } }; + } - updateSizeAndPosition(); - + protected override void LoadComplete() + { chatBoxManager.SelectedClip.BindValueChanged(e => { ((Container)Child).BorderThickness = Clip == e.NewValue ? 4 : 2; @@ -97,6 +96,8 @@ private void load() Clip.Name.BindValueChanged(e => drawName.Text = e.NewValue, true); Clip.Enabled.BindValueChanged(e => Child.FadeTo(e.NewValue ? 1 : 0.5f), true); + + updateSizeAndPosition(); } protected override bool OnMouseDown(MouseDownEvent e) @@ -171,7 +172,7 @@ private void load() { Child = new Box { - Colour = Color4.Black.Opacity(0f), + Colour = Color4.Black.Opacity(0.2f), RelativeSizeAxes = Axes.Both }; } @@ -186,7 +187,7 @@ protected override bool OnHover(HoverEvent e) protected override void OnHoverLost(HoverLostEvent e) { - Child.FadeColour(Colour4.Black.Opacity(0f), 100); + Child.FadeColour(Colour4.Black.Opacity(0.2f), 100); } } @@ -204,6 +205,8 @@ private partial class StartResizeDetector : ResizeDetector public StartResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; } protected override void OnDrag(DragEvent e) @@ -245,6 +248,8 @@ private partial class EndResizeDetector : ResizeDetector public EndResizeDetector(Clip clip, Func normaliseFunc) : base(clip, normaliseFunc) { + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; } protected override void OnDrag(DragEvent e) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs index 40f6b52f..9e1c132c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Clip/TimelineClipMenu.cs @@ -13,7 +13,7 @@ public partial class TimelineClipMenu : TimelineMenu [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; - private Game.ChatBox.Clips.Clip clip; + private Game.ChatBox.Clips.Clip clip { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -49,6 +49,27 @@ private void load() Action = () => chatBoxManager.DecreasePriority(clip) } }); + + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 25, + Child = new MenuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Delete", + FontSize = 20, + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Action = () => + { + chatBoxManager.DeleteClip(clip); + if (chatBoxManager.SelectedClip.Value == clip) chatBoxManager.SelectedClip.Value = null; + Hide(); + } + } + }); } public void SetClip(Game.ChatBox.Clips.Clip clip) diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index d6699b0c..7673fa2c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -14,7 +14,7 @@ public partial class TimelineLayerMenu : TimelineMenu private ChatBoxManager chatBoxManager { get; set; } = null!; public int XPos; - public TimelineLayer Layer; + public TimelineLayer Layer { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -38,7 +38,7 @@ private void load() private void createClip() { - var clip = chatBoxManager.CreateClip(); + var clip = new Game.ChatBox.Clips.Clip(chatBoxManager); var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false, true); diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 9a402d88..c496babb 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -1,7 +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 System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -30,12 +30,9 @@ public partial class TimelineEditor : Container [Resolved] private TimelineClipMenu clipMenu { get; set; } = null!; - private TimelineLayer layer5 = null!; - private TimelineLayer layer4 = null!; - private TimelineLayer layer3 = null!; - private TimelineLayer layer2 = null!; - private TimelineLayer layer1 = null!; - private TimelineLayer layer0 = null!; + private const int grid_line_width = 3; + + private Dictionary layers = new(); private Container gridGenerator = null!; [BackgroundDependencyLoader] @@ -44,6 +41,8 @@ private void load() Masking = true; CornerRadius = 10; + GridContainer layerContainer; + Children = new Drawable[] { new Box @@ -54,12 +53,12 @@ private void load() gridGenerator = new Container { RelativeSizeAxes = Axes.Both, - Position = new Vector2(-2.5f) + Position = new Vector2(-(grid_line_width / 2f)) }, new Container { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + Child = layerContainer = new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] @@ -73,66 +72,45 @@ private void load() }, Content = new[] { - new Drawable[] - { - layer5 = new TimelineLayer - { - Priority = 5 - } - }, - new Drawable[] - { - layer4 = new TimelineLayer - { - Priority = 4 - } - }, - new Drawable[] - { - layer3 = new TimelineLayer - { - Priority = 3 - } - }, - new Drawable[] - { - layer2 = new TimelineLayer - { - Priority = 2 - } - }, - new Drawable[] - { - layer1 = new TimelineLayer - { - Priority = 1 - } - }, - new Drawable[] - { - layer0 = new TimelineLayer - { - Priority = 0 - } - } + new Drawable[] { new TimelineLayer(5) }, + new Drawable[] { new TimelineLayer(4) }, + new Drawable[] { new TimelineLayer(3) }, + new Drawable[] { new TimelineLayer(2) }, + new Drawable[] { new TimelineLayer(1) }, + new Drawable[] { new TimelineLayer(0) } } } } }; - chatBoxManager.Clips.BindCollectionChanged((_, e) => + foreach (var array in layerContainer.Content) { - if (e.NewItems == null) return; + var timelineLayer = (TimelineLayer)array[0]; + layers.Add(timelineLayer.Priority, timelineLayer); + } - foreach (Clip newClip in e.NewItems) + chatBoxManager.Clips.BindCollectionChanged((_, e) => + { + if (e.OldItems is not null) { - getLayer(newClip.Priority.Value).Add(newClip); + foreach (Clip oldClip in e.OldItems) + { + layers[oldClip.Priority.Value].Remove(oldClip); + } + } - newClip.Priority.BindValueChanged(e => + if (e.NewItems is not null) + { + foreach (Clip newClip in e.NewItems) { - getLayer(e.OldValue).Remove(newClip); - getLayer(e.NewValue).Add(newClip); - }); + layers[newClip.Priority.Value].Add(newClip); + + newClip.Priority.BindValueChanged(priorityValue => + { + layers[priorityValue.OldValue].Remove(newClip); + layers[priorityValue.NewValue].Add(newClip); + }); + } } }, true); @@ -148,7 +126,7 @@ private void generateGrid() Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.5f), RelativeSizeAxes = Axes.Y, RelativePositionAxes = Axes.X, - Width = 5, + Width = grid_line_width, X = (chatBoxManager.TimelineResolution * i) }); } @@ -160,26 +138,12 @@ private void generateGrid() Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.5f), RelativeSizeAxes = Axes.X, RelativePositionAxes = Axes.Y, - Height = 5, + Height = grid_line_width, Y = (DrawHeight / 6 * i) }); } } - private TimelineLayer getLayer(int priority) - { - return priority switch - { - 0 => layer0, - 1 => layer1, - 2 => layer2, - 3 => layer3, - 4 => layer4, - 5 => layer5, - _ => throw new InvalidOperationException($"No layer with priority {priority}") - }; - } - public void ShowLayerMenu(MouseDownEvent e, int xPos, TimelineLayer layer) { clipMenu.Hide(); diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs index b71d73e3..29645ed1 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineLayer.cs @@ -20,7 +20,12 @@ public partial class TimelineLayer : Container [Resolved] private TimelineEditor timelineEditor { get; set; } = null!; - public required int Priority { get; init; } + public readonly int Priority; + + public TimelineLayer(int priority) + { + Priority = priority; + } [BackgroundDependencyLoader] private void load() diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineWrapper.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineWrapper.cs new file mode 100644 index 00000000..668f9a5b --- /dev/null +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineWrapper.cs @@ -0,0 +1,40 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.ChatBox.Timeline; + +public partial class TimelineWrapper : Container +{ + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Dark], + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = new TimelineEditor + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + } + } + }; + } +} diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs index 729440a6..01651d93 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs @@ -40,7 +40,7 @@ private void load() protected override void LoadComplete() { base.LoadComplete(); - textBox.OnValidEntry += entry => UpdateAttribute(entry); + textBox.OnValidEntry += entry => UpdateAttribute(entry!); } protected override void SetDefault() diff --git a/VRCOSC.Game/Graphics/Settings/AutomationSection.cs b/VRCOSC.Game/Graphics/Settings/AutomationSection.cs index ab147a8d..18cc830b 100644 --- a/VRCOSC.Game/Graphics/Settings/AutomationSection.cs +++ b/VRCOSC.Game/Graphics/Settings/AutomationSection.cs @@ -11,7 +11,7 @@ public sealed partial class AutomationSection : SectionContainer protected override void GenerateItems() { - AddToggle("Start/Stop with VRChat", "Auto start/stop modules on VRChat open/close", ConfigManager.GetBindable(VRCOSCSetting.AutoStartStop)); + AddToggle("Start/Stop with VRChat", "Auto start/stop modules on VRChat open/close (Note: Disables run button from being used manually)", ConfigManager.GetBindable(VRCOSCSetting.AutoStartStop)); AddToggle("Open with SteamVR", "Should VRCOSC open when SteamVR launches? Requires a manual launch when changing to true", ConfigManager.GetBindable(VRCOSCSetting.AutoStartOpenVR)); AddToggle("Close with SteamVR", "Should VRCOSC close when SteamVR closes?", ConfigManager.GetBindable(VRCOSCSetting.AutoStopOpenVR)); } diff --git a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs index 29076866..3fc9121a 100644 --- a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs @@ -26,6 +26,7 @@ public ToggleButton() private void load() { SpriteIcon icon; + Children = new Drawable[] { new Box diff --git a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs index c6d538ad..ba54fa6e 100644 --- a/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs +++ b/VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs @@ -7,38 +7,15 @@ namespace VRCOSC.Game.Modules.ChatBox; public abstract class ChatBoxModule : Module { - protected void CreateVariable(Enum lookup, string name, string format) - { - ChatBoxManager.RegisterVariable(SerialisedName, lookup.ToLookup(), name, format); - } + protected void CreateVariable(Enum lookup, string name, string format) => ChatBoxManager.RegisterVariable(SerialisedName, lookup.ToLookup(), name, format); + protected void CreateState(Enum lookup, string name, string defaultFormat) => ChatBoxManager.RegisterState(SerialisedName, lookup.ToLookup(), name, defaultFormat); + protected void CreateEvent(Enum lookup, string name, string defaultFormat, int defaultLength) => ChatBoxManager.RegisterEvent(SerialisedName, lookup.ToLookup(), name, defaultFormat, defaultLength); - protected void SetVariableValue(Enum lookup, string? value) - { - ChatBoxManager.SetVariable(SerialisedName, lookup.ToLookup(), value); - } + protected void SetVariableValue(Enum lookup, string? value) => ChatBoxManager.SetVariable(SerialisedName, lookup.ToLookup(), value); + protected string GetVariableFormat(Enum lookup) => ChatBoxManager.VariableMetadata[SerialisedName][lookup.ToLookup()].DisplayableFormat; - protected string GetVariableFormat(Enum lookup) - { - return ChatBoxManager.VariableMetadata[SerialisedName][lookup.ToLookup()].DisplayableFormat; - } + protected void ChangeStateTo(Enum lookup) => ChatBoxManager.ChangeStateTo(SerialisedName, lookup.ToLookup()); + protected void TriggerEvent(Enum lookup) => ChatBoxManager.TriggerEvent(SerialisedName, lookup.ToLookup()); - protected void CreateState(Enum lookup, string name, string defaultFormat) - { - ChatBoxManager.RegisterState(SerialisedName, lookup.ToLookup(), name, defaultFormat); - } - - protected void ChangeStateTo(Enum lookup) - { - ChatBoxManager.ChangeStateTo(SerialisedName, lookup.ToLookup()); - } - - protected void CreateEvent(Enum lookup, string name, string defaultFormat, int defaultLength) - { - ChatBoxManager.RegisterEvent(SerialisedName, lookup.ToLookup(), name, defaultFormat, defaultLength); - } - - protected void TriggerEvent(Enum lookup) - { - ChatBoxManager.TriggerEvent(SerialisedName, lookup.ToLookup()); - } + protected void SetChatBoxTyping(bool typing) => ChatBoxManager.SetTyping(typing); } diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 629f7198..f6422ffb 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -95,6 +95,8 @@ private void load() OVRHelper.OnError += m => Logger.Log($"[OpenVR] {m}"); setupModules(); + + chatBoxManager.Load(); } private void setupModules() @@ -213,7 +215,7 @@ private async Task startAsync() VRChatOscClient.Enable(OscClientFlag.Send); Player.Initialise(); - ChatBoxManager.Initialise(moduleEnabled); + ChatBoxManager.Initialise(VRChatOscClient, configManager.GetBindable(VRCOSCSetting.ChatBoxTimeSpan), moduleEnabled); sendControlValues(); ModuleManager.Start(); VRChatOscClient.Enable(OscClientFlag.Receive); diff --git a/VRCOSC.Game/Modules/Manager/IModuleManager.cs b/VRCOSC.Game/Modules/Manager/IModuleManager.cs index 141f087f..7539b3ef 100644 --- a/VRCOSC.Game/Modules/Manager/IModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/IModuleManager.cs @@ -22,4 +22,5 @@ public interface IModuleManager : IEnumerable public void Update(); public void Stop(); public IEnumerable GetEnabledModuleNames(); + public string GetModuleName(string serialisedName); } diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index b49a424f..b9261ac5 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -101,6 +101,8 @@ public void Stop() public IEnumerable GetEnabledModuleNames() => modules.Where(module => module.Enabled.Value).Select(module => module.SerialisedName); + public string GetModuleName(string serialisedName) => modules.Single(module => module.SerialisedName == serialisedName).Title; + public IEnumerator GetEnumerator() => modules.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index c0349c4d..860df821 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -50,8 +50,9 @@ public abstract class Module : IComparable private bool IsEnabled => Enabled.Value; private bool ShouldUpdate => DeltaUpdate != TimeSpan.MaxValue; - internal string SerialisedName => GetType().Name; - internal string FileName => @$"{SerialisedName}.ini"; + internal string Name => GetType().Name; + internal string SerialisedName => Name.ToLowerInvariant(); + internal string FileName => @$"{Name}.ini"; protected bool IsStarting => State.Value == ModuleState.Starting; protected bool HasStarted => State.Value == ModuleState.Started; diff --git a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs index 55b77bb4..75ccc0fa 100644 --- a/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs +++ b/VRCOSC.Modules/ChatBoxText/ChatBoxTextModule.cs @@ -12,7 +12,6 @@ public class ChatBoxTextModule : ChatBoxModule public override string Author => "VolcanicArts"; public override ModuleType Type => ModuleType.General; protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(1.5f); - protected override bool ShouldUpdateImmediately => false; private int index; @@ -31,7 +30,7 @@ protected override void CreateAttributes() protected override void OnModuleStart() { - index = 0; + index = -1; ChangeStateTo(ChatBoxTextState.Default); } From 5cb3aaaa75f420a167dbb14d8ec9c30ee640c34f Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 12:59:15 +0100 Subject: [PATCH 42/59] Implement serialisation --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 103 +++++++++++++++++- VRCOSC.Game/ChatBox/Clips/Clip.cs | 62 ++++++++--- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 2 +- VRCOSC.Game/ChatBox/Clips/ClipState.cs | 6 +- VRCOSC.Game/ChatBox/DefaultTimeline.cs | 92 +++++++--------- .../Serialisation/ITimelineSerialiser.cs | 14 +++ .../V1/Structures/SerialisableClip.cs | 54 +++++++++ .../V1/Structures/SerialisableClipEvent.cs | 39 +++++++ .../V1/Structures/SerialisableClipState.cs | 52 +++++++++ .../V1/Structures/SerialisableTimeline.cs | 27 +++++ .../Serialisation/V1/TimelineSerialiser.cs | 44 ++++++++ .../ChatBox/Metadata/MetadataToggle.cs | 2 +- .../SelectedClip/DrawableAssociatedModule.cs | 4 +- .../ChatBox/SelectedClip/DrawableEvent.cs | 3 +- .../ChatBox/SelectedClip/DrawableState.cs | 8 +- .../SelectedClipMetadataEditor.cs | 3 +- .../Timeline/Menu/Layer/TimelineLayerMenu.cs | 2 +- .../Graphics/ModuleListing/ModuleCard.cs | 2 +- .../Graphics/UI/Button/ToggleButton.cs | 4 +- VRCOSC.Game/Modules/GameManager.cs | 2 +- 20 files changed, 433 insertions(+), 92 deletions(-) create mode 100644 VRCOSC.Game/ChatBox/Serialisation/ITimelineSerialiser.cs create mode 100644 VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs create mode 100644 VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipEvent.cs create mode 100644 VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipState.cs create mode 100644 VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableTimeline.cs create mode 100644 VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 9c3b4764..59ca6785 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -3,10 +3,13 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Platform; using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.ChatBox.Serialisation.V1; using VRCOSC.Game.OSC.VRChat; namespace VRCOSC.Game.ChatBox; @@ -37,6 +40,7 @@ public bool SendEnabled public IReadOnlyDictionary ModuleEnabledCache = null!; private Bindable sendDelay = null!; private VRChatOscClient oscClient = null!; + private TimelineSerialiser serialiser = null!; public readonly Dictionary<(string, string), string?> VariableValues = new(); public readonly Dictionary StateValues = new(); @@ -52,11 +56,97 @@ public bool SendEnabled private DateTimeOffset startTime; private DateTimeOffset nextValidTime; private bool isClear; + private bool isLoaded; - public void Load() + public void Load(Storage storage) { - // TODO Check for serialised file. If no file, generate default timeline - Clips.AddRange(DefaultTimeline.GenerateDefaultTimeline(this)); + serialiser = new TimelineSerialiser(storage); + + if (storage.Exists(@"chatbox.json")) + loadClipData(); + else + Clips.AddRange(DefaultTimeline.GenerateDefaultTimeline(this)); + + Clips.BindCollectionChanged((_, _) => Save()); + + isLoaded = true; + + Save(); + } + + private void loadClipData() + { + var clips = serialiser.Deserialise()?.Clips; + + if (clips is null) return; + + clips.ForEach(clip => + { + clip.AssociatedModules.ToImmutableList().ForEach(moduleName => + { + if (!StateMetadata.ContainsKey(moduleName)) + { + clip.AssociatedModules.Remove(moduleName); + + clip.States.ToImmutableList().ForEach(clipState => + { + clipState.States.RemoveAll(pair => pair.Module == moduleName); + }); + + clip.Events.RemoveAll(clipEvent => clipEvent.Module == moduleName); + + return; + } + + clip.States.ToImmutableList().ForEach(clipState => + { + clipState.States.RemoveAll(pair => !StateMetadata[pair.Module].ContainsKey(pair.Lookup)); + }); + + clip.Events.RemoveAll(clipEvent => !EventMetadata[clipEvent.Module].ContainsKey(clipEvent.Lookup)); + }); + }); + + clips.ForEach(clip => + { + var newClip = CreateClip(); + + newClip.Enabled.Value = clip.Enabled; + newClip.Name.Value = clip.Name; + newClip.Priority.Value = clip.Priority; + newClip.Start.Value = clip.Start; + newClip.End.Value = clip.End; + + newClip.AssociatedModules.AddRange(clip.AssociatedModules); + + clip.States.ForEach(clipState => + { + var stateData = newClip.GetStateFor(clipState.States.Select(state => state.Module), clipState.States.Select(state => state.Lookup)); + if (stateData is null) return; + + stateData.Enabled.Value = clipState.Enabled; + stateData.Format.Value = clipState.Format; + }); + + clip.Events.ForEach(clipEvent => + { + var eventData = newClip.GetEventFor(clipEvent.Module, clipEvent.Lookup); + if (eventData is null) return; + + eventData.Enabled.Value = clipEvent.Enabled; + eventData.Format.Value = clipEvent.Format; + eventData.Length.Value = clipEvent.Length; + }); + + Clips.Add(newClip); + }); + } + + public void Save() + { + if (!isLoaded) return; + + serialiser.Serialise(Clips.ToList()); } public void Initialise(VRChatOscClient oscClient, Bindable sendDelay, Dictionary moduleEnabledCache) @@ -71,6 +161,13 @@ public void Initialise(VRChatOscClient oscClient, Bindable sendDelay, Dicti Clips.ForEach(clip => clip.Initialise()); } + public Clip CreateClip() + { + var newClip = new Clip(); + newClip.InjectDependencies(this); + return newClip; + } + public void Update() { lock (triggeredEventsLock) diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index 2db4170e..cf23ac73 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -16,27 +16,33 @@ namespace VRCOSC.Game.ChatBox.Clips; /// public class Clip { - public readonly BindableBool Enabled = new(true); + public readonly Bindable Enabled = new(true); public readonly Bindable Name = new("New Clip"); public readonly BindableNumber Priority = new(); public readonly BindableList AssociatedModules = new(); - public readonly BindableList AvailableVariables = new(); - public readonly BindableList States = new(); - public readonly BindableList Events = new(); public readonly Bindable Start = new(); public readonly Bindable End = new(); - public int Length => End.Value - Start.Value; - - private readonly ChatBoxManager chatBoxManager; + public readonly BindableList States = new(); + public readonly BindableList Events = new(); + public readonly BindableList AvailableVariables = new(); + public int Length => End.Value - Start.Value; + private ChatBoxManager chatBoxManager = null!; private readonly Queue eventQueue = new(); private (ClipEvent, DateTimeOffset)? currentEvent; private ClipState? currentState; - public Clip(ChatBoxManager chatBoxManager) + public void InjectDependencies(ChatBoxManager chatBoxManager) { this.chatBoxManager = chatBoxManager; AssociatedModules.BindCollectionChanged((_, e) => onAssociatedModulesChanged(e), true); + AssociatedModules.BindCollectionChanged((_, _) => chatBoxManager.Save()); + Enabled.BindValueChanged(_ => chatBoxManager.Save()); + Name.BindValueChanged(_ => chatBoxManager.Save()); + Start.BindValueChanged(_ => chatBoxManager.Save()); + End.BindValueChanged(_ => chatBoxManager.Save()); + States.BindCollectionChanged((_, _) => chatBoxManager.Save()); + Events.BindCollectionChanged((_, _) => chatBoxManager.Save()); } public void Initialise() @@ -84,10 +90,31 @@ private void setCurrentEvent() } } - public ClipState GetStateFor(string module, string lookup) => getStateFor(new List { module }, new List { lookup }); + public ClipEvent? GetEventFor(string module, string lookup) + { + try + { + return Events.Single(clipEvent => clipEvent.Module == module && clipEvent.Lookup == lookup); + } + catch (InvalidOperationException) + { + return null; + } + } + + public ClipState? GetStateFor(string module, string lookup) => GetStateFor(new List { module }, new List { lookup }); - private ClipState getStateFor(IReadOnlyCollection modules, IReadOnlyCollection lookups) => - States.Single(clipState => clipState.ModuleNames.SequenceEqual(modules) && clipState.StateNames.SequenceEqual(lookups)); + public ClipState? GetStateFor(IEnumerable modules, IEnumerable lookups) + { + try + { + return States.Single(clipState => clipState.ModuleNames.SequenceEqual(modules) && clipState.StateNames.SequenceEqual(lookups)); + } + catch (InvalidOperationException) + { + return null; + } + } public bool Evalulate() { @@ -218,10 +245,15 @@ private void addStatesOfAddedModule(string moduleName) localCurrentStatesCopy.ForEach(newStateLocal => { newStateLocal.States.Add((moduleName, newStateName)); + newStateLocal.Enabled.BindValueChanged(_ => chatBoxManager.Save()); + newStateLocal.Format.BindValueChanged(_ => chatBoxManager.Save()); }); States.AddRange(localCurrentStatesCopy); - States.Add(new ClipState(newStateMetadata)); + var singleState = new ClipState(newStateMetadata); + singleState.Enabled.BindValueChanged(_ => chatBoxManager.Save()); + singleState.Format.BindValueChanged(_ => chatBoxManager.Save()); + States.Add(singleState); } } @@ -247,7 +279,11 @@ private void addEventsOfAddedModules(NotifyCollectionChangedEventArgs e) foreach (var (_, metadata) in events) { - Events.Add(new ClipEvent(metadata)); + var newEvent = new ClipEvent(metadata); + newEvent.Enabled.BindValueChanged(_ => chatBoxManager.Save()); + newEvent.Format.BindValueChanged(_ => chatBoxManager.Save()); + newEvent.Length.BindValueChanged(_ => chatBoxManager.Save()); + Events.Add(newEvent); } } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index a858b360..834734dc 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -17,7 +17,7 @@ public class ClipEvent Value = string.Empty }; - public BindableBool Enabled = new(); + public Bindable Enabled = new(); public Bindable Length = new(); public ClipEvent(ClipEventMetadata metadata) diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index b214d17a..cafa1604 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -17,7 +17,7 @@ public class ClipState Value = string.Empty }; - public BindableBool Enabled = new(); + public Bindable Enabled = new(); public List ModuleNames => States.Select(state => state.Item1).ToList(); public List StateNames => States.Select(state => state.Item2).ToList(); @@ -35,7 +35,7 @@ public ClipState Copy(bool includeData = false) if (includeData) { copy.Format = Format.GetUnboundCopy(); - copy.Enabled = (BindableBool)Enabled.GetUnboundCopy(); + copy.Enabled = Enabled.GetUnboundCopy(); } return copy; @@ -45,7 +45,7 @@ private ClipState() { } public ClipState(ClipStateMetadata metadata) { - States = new List<(string, string)> { (metadata.Module, metadata.Lookup) }; + States = new List<(string, string)> { new(metadata.Module, metadata.Lookup) }; Format.Value = metadata.DefaultFormat; Format.Default = metadata.DefaultFormat; } diff --git a/VRCOSC.Game/ChatBox/DefaultTimeline.cs b/VRCOSC.Game/ChatBox/DefaultTimeline.cs index fb3d0b86..840d748a 100644 --- a/VRCOSC.Game/ChatBox/DefaultTimeline.cs +++ b/VRCOSC.Game/ChatBox/DefaultTimeline.cs @@ -20,98 +20,80 @@ public static IEnumerable GenerateDefaultTimeline(ChatBoxManager chatBoxMa private static Clip generateClockClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"Clock" }, - Priority = { Value = 0 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"Clock"; + clip.Priority.Value = 0; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"clockmodule"); - clip.GetStateFor(@"clockmodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"clockmodule", @"default")!.Enabled.Value = true; return clip; } private static Clip generateHwsClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"Hardware Stats" }, - Priority = { Value = 1 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"Hardware Stats"; + clip.Priority.Value = 1; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"hardwarestatsmodule"); - clip.GetStateFor(@"hardwarestatsmodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"hardwarestatsmodule", @"default")!.Enabled.Value = true; return clip; } private static Clip generateWeatherClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"Weather" }, - Priority = { Value = 2 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"Weather"; + clip.Priority.Value = 2; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"weathermodule"); - clip.GetStateFor(@"weathermodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"weathermodule", @"default")!.Enabled.Value = true; return clip; } private static Clip generateHeartrateClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"Heartrate" }, - Priority = { Value = 3 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"Heartrate"; + clip.Priority.Value = 3; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"hyperatemodule"); clip.AssociatedModules.Add(@"pulsoidmodule"); - clip.GetStateFor(@"hyperatemodule", @"default").Enabled.Value = true; - clip.GetStateFor(@"pulsoidmodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"hyperatemodule", @"default")!.Enabled.Value = true; + clip.GetStateFor(@"pulsoidmodule", @"default")!.Enabled.Value = true; return clip; } private static Clip generateChatBoxTextClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"ChatBox Text" }, - Priority = { Value = 4 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"ChatBox Text"; + clip.Priority.Value = 4; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"chatboxtextmodule"); - clip.GetStateFor(@"chatboxtextmodule", @"default").Enabled.Value = true; + clip.GetStateFor(@"chatboxtextmodule", @"default")!.Enabled.Value = true; return clip; } private static Clip generateMediaClip(ChatBoxManager chatBoxManager) { - var clip = new Clip(chatBoxManager) - { - Name = { Value = @"Media" }, - Priority = { Value = 5 }, - Start = { Value = 0 }, - End = { Value = 60 } - }; - + var clip = chatBoxManager.CreateClip(); + clip.Name.Value = @"Media"; + clip.Priority.Value = 5; + clip.Start.Value = 0; + clip.End.Value = 60; clip.AssociatedModules.Add(@"mediamodule"); - clip.GetStateFor(@"mediamodule", @"playing").Enabled.Value = true; + clip.GetStateFor(@"mediamodule", @"playing")!.Enabled.Value = true; return clip; } diff --git a/VRCOSC.Game/ChatBox/Serialisation/ITimelineSerialiser.cs b/VRCOSC.Game/ChatBox/Serialisation/ITimelineSerialiser.cs new file mode 100644 index 00000000..09e5da14 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/ITimelineSerialiser.cs @@ -0,0 +1,14 @@ +// 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 VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +namespace VRCOSC.Game.ChatBox.Serialisation; + +public interface ITimelineSerialiser +{ + public void Serialise(List clips); + public SerialisableTimeline? Deserialise(); +} diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs new file mode 100644 index 00000000..a583a2e7 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs @@ -0,0 +1,54 @@ +// 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 System.Linq; +using Newtonsoft.Json; +using osu.Framework.Extensions.IEnumerableExtensions; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +public class SerialisableClip +{ + [JsonProperty("enabled")] + public bool Enabled; + + [JsonProperty("name")] + public string Name = null!; + + [JsonProperty("priority")] + public int Priority; + + [JsonProperty("associated_modules")] + public List AssociatedModules = new(); + + [JsonProperty("start")] + public int Start; + + [JsonProperty("end")] + public int End; + + [JsonProperty("states")] + public List States = new(); + + [JsonProperty("events")] + public List Events = new(); + + [JsonConstructor] + public SerialisableClip() + { + } + + public SerialisableClip(Clip clip) + { + Enabled = clip.Enabled.Value; + Name = clip.Name.Value; + Priority = clip.Priority.Value; + AssociatedModules = clip.AssociatedModules.ToList(); + Start = clip.Start.Value; + End = clip.End.Value; + clip.States.ForEach(clipState => States.Add(new SerialisableClipState(clipState))); + clip.Events.ForEach(clipEvent => Events.Add(new SerialisableClipEvent(clipEvent))); + } +} diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipEvent.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipEvent.cs new file mode 100644 index 00000000..86df31ea --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipEvent.cs @@ -0,0 +1,39 @@ +// 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; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +public class SerialisableClipEvent +{ + [JsonProperty("module")] + public string Module = null!; + + [JsonProperty("lookup")] + public string Lookup = null!; + + [JsonProperty("format")] + public string Format = null!; + + [JsonProperty("enabled")] + public bool Enabled; + + [JsonProperty("length")] + public int Length; + + [JsonConstructor] + public SerialisableClipEvent() + { + } + + public SerialisableClipEvent(ClipEvent clipEvent) + { + Module = clipEvent.Module; + Lookup = clipEvent.Lookup; + Format = clipEvent.Format.Value; + Enabled = clipEvent.Enabled.Value; + Length = clipEvent.Length.Value; + } +} diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipState.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipState.cs new file mode 100644 index 00000000..4153c172 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClipState.cs @@ -0,0 +1,52 @@ +// 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 Newtonsoft.Json; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +public class SerialisableClipState +{ + [JsonProperty("states")] + public List States = new(); + + [JsonProperty("format")] + public string Format = null!; + + [JsonProperty("enabled")] + public bool Enabled; + + [JsonConstructor] + public SerialisableClipState() + { + } + + public SerialisableClipState(ClipState clipState) + { + clipState.States.ForEach(pair => States.Add(new SerialisableClipStateStates(pair))); + Format = clipState.Format.Value; + Enabled = clipState.Enabled.Value; + } +} + +public class SerialisableClipStateStates +{ + [JsonProperty("module")] + public string Module = null!; + + [JsonProperty("lookup")] + public string Lookup = null!; + + [JsonConstructor] + public SerialisableClipStateStates() + { + } + + public SerialisableClipStateStates((string, string) pair) + { + Module = pair.Item1; + Lookup = pair.Item2; + } +} diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableTimeline.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableTimeline.cs new file mode 100644 index 00000000..a05349dd --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableTimeline.cs @@ -0,0 +1,27 @@ +// 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 Newtonsoft.Json; +using VRCOSC.Game.ChatBox.Clips; + +namespace VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +public class SerialisableTimeline +{ + [JsonProperty("version")] + public int Version = 1; + + [JsonProperty("clips")] + public List Clips = new(); + + [JsonConstructor] + public SerialisableTimeline() + { + } + + public SerialisableTimeline(List clips) + { + clips.ForEach(clip => Clips.Add(new SerialisableClip(clip))); + } +} diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs new file mode 100644 index 00000000..6af30c42 --- /dev/null +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs @@ -0,0 +1,44 @@ +// 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 System.IO; +using Newtonsoft.Json; +using osu.Framework.Platform; +using VRCOSC.Game.ChatBox.Clips; +using VRCOSC.Game.ChatBox.Serialisation.V1.Structures; + +namespace VRCOSC.Game.ChatBox.Serialisation.V1; + +public class TimelineSerialiser : ITimelineSerialiser +{ + private const string file_name = @"chatbox.json"; + private readonly Storage storage; + + public TimelineSerialiser(Storage storage) + { + this.storage = storage; + } + + public void Serialise(List clips) + { + using var stream = storage.CreateFileSafely(file_name); + using var writer = new StreamWriter(stream); + writer.Write(JsonConvert.SerializeObject(new SerialisableTimeline(clips))); + } + + public SerialisableTimeline? Deserialise() + { + using (var stream = storage.GetStream(file_name)) + { + if (stream is not null) + { + using var reader = new StreamReader(stream); + + return JsonConvert.DeserializeObject(reader.ReadToEnd()); + } + } + + return null; + } +} diff --git a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs index 324d6e85..20645117 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Metadata/MetadataToggle.cs @@ -15,7 +15,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.Metadata; public partial class MetadataToggle : Container { public required string Label { get; init; } - public required BindableBool State { get; init; } + public required Bindable State { get; init; } [BackgroundDependencyLoader] private void load() diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs index 3df14d69..64772efe 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableAssociatedModule.cs @@ -15,7 +15,7 @@ namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; public partial class DrawableAssociatedModule : Container { public required string ModuleName { get; init; } - public readonly BindableBool State = new(); + public readonly Bindable State = new(); [BackgroundDependencyLoader] private void load() @@ -69,7 +69,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = State + State = State.GetBoundCopy() } } } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs index 815cce2b..823e327b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableEvent.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -69,7 +68,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = (BindableBool)clipEvent.Enabled.GetBoundCopy() + State = clipEvent.Enabled.GetBoundCopy() } }, new Container diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs index c933e07a..47552984 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -39,9 +38,8 @@ private void load() clipState.States.ForEach(pair => { - var (moduleName, lookup) = pair; - var stateMetadata = chatBoxManager.StateMetadata[moduleName][lookup]; - stateNameList += gameManager.ModuleManager.GetModuleName(moduleName); + var stateMetadata = chatBoxManager.StateMetadata[pair.Item1][pair.Item2]; + stateNameList += gameManager.ModuleManager.GetModuleName(pair.Item1); if (stateMetadata.Name != "Default") stateNameList += " - " + stateMetadata.Name; stateNameList += " & "; }); @@ -84,7 +82,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = (BindableBool)clipState.Enabled.GetBoundCopy() + State = clipState.Enabled.GetBoundCopy() } }, new Container diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 79a7f139..634dcb6a 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -100,7 +99,7 @@ private void onSelectedClipChange(Clip? clip) metadataFlow.Add(new MetadataToggle { Label = "Enabled", - State = (BindableBool)clip.Enabled.GetBoundCopy() + State = clip.Enabled.GetBoundCopy() }); metadataFlow.Add(new MetadataString diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs index 7673fa2c..0b2c5718 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/Menu/Layer/TimelineLayerMenu.cs @@ -38,7 +38,7 @@ private void load() private void createClip() { - var clip = new Game.ChatBox.Clips.Clip(chatBoxManager); + var clip = chatBoxManager.CreateClip(); var (lowerBound, upperBound) = Layer.GetBoundsNearestTo(XPos, false, true); diff --git a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs index 17472bfa..9f5f22c2 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs @@ -81,7 +81,7 @@ public ModuleCard(Module module) Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, ShouldAnimate = false, - State = (BindableBool)Module.Enabled.GetBoundCopy() + State = Module.Enabled.GetBoundCopy() } }, new FillFlowContainer diff --git a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs index 3fc9121a..e05c9217 100644 --- a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs @@ -14,7 +14,7 @@ namespace VRCOSC.Game.Graphics.UI.Button; public sealed partial class ToggleButton : VRCOSCButton { - public BindableBool State { get; init; } = new(); + public Bindable State { get; init; } = new(); public ToggleButton() { @@ -59,7 +59,7 @@ private void load() protected override bool OnClick(ClickEvent e) { - State.Toggle(); + State.Value = !State.Value; return base.OnClick(e); } } diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index f6422ffb..6043950c 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -96,7 +96,7 @@ private void load() setupModules(); - chatBoxManager.Load(); + chatBoxManager.Load(storage); } private void setupModules() From b27fe279581e050987f762177c88af39443933dd Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 13:31:04 +0100 Subject: [PATCH 43/59] Add live preview indicator --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 1 + .../ChatBox/Timeline/TimelineEditor.cs | 34 +++++++++++++++++++ VRCOSC.Game/VRCOSCGame.cs | 1 + 3 files changed, 36 insertions(+) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 59ca6785..8a46a9cc 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -50,6 +50,7 @@ public bool SendEnabled public readonly Bindable TimelineLength = new(TimeSpan.FromMinutes(1)); public float TimelineResolution => 1f / (float)TimelineLength.Value.TotalSeconds; + public float CurrentPercentage => ((DateTimeOffset.Now - startTime).Ticks % TimelineLength.Value.Ticks) / (float)TimelineLength.Value.Ticks; public int CurrentSecond => (int)Math.Floor((DateTimeOffset.Now - startTime).TotalSeconds) % (int)TimelineLength.Value.TotalSeconds; private bool sendAllowed => nextValidTime <= DateTimeOffset.Now; diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index c496babb..42a817f3 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -15,6 +15,7 @@ using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Clip; using VRCOSC.Game.Graphics.ChatBox.Timeline.Menu.Layer; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ChatBox.Timeline; @@ -24,6 +25,9 @@ public partial class TimelineEditor : Container [Resolved] private ChatBoxManager chatBoxManager { get; set; } = null!; + [Resolved] + private GameManager gameManager { get; set; } = null!; + [Resolved] private TimelineLayerMenu layerMenu { get; set; } = null!; @@ -34,6 +38,7 @@ public partial class TimelineEditor : Container private Dictionary layers = new(); private Container gridGenerator = null!; + private Container positionIndicator = null!; [BackgroundDependencyLoader] private void load() @@ -80,6 +85,22 @@ private void load() new Drawable[] { new TimelineLayer(0) } } } + }, + positionIndicator = new Container + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 5, + CornerRadius = 2, + EdgeEffect = VRCOSCEdgeEffects.BasicShadow, + Masking = true, + Child = new Box + { + Colour = ThemeManager.Current[ThemeAttribute.Accent], + RelativeSizeAxes = Axes.Both + } } }; @@ -117,6 +138,19 @@ private void load() generateGrid(); } + protected override void Update() + { + if (gameManager.State.Value == GameManagerState.Started) + { + positionIndicator.Alpha = 1; + positionIndicator.X = chatBoxManager.CurrentPercentage; + } + else + { + positionIndicator.Alpha = 0; + } + } + private void generateGrid() { for (var i = 0; i < 60; i++) diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index 0394b55f..159a633f 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -190,6 +190,7 @@ private void performExit() editingModule.Value = null; infoModule.Value = null; routerManager.SaveData(); + ChatBoxManager.Save(); Exit(); } From a678773ecdff927e224865adc391804ddbe2e3bd Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 13:42:06 +0100 Subject: [PATCH 44/59] Don't serialise default states/events --- VRCOSC.Game/ChatBox/Clips/ClipEvent.cs | 3 +++ VRCOSC.Game/ChatBox/Clips/ClipState.cs | 2 ++ .../Serialisation/V1/Structures/SerialisableClip.cs | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs index 834734dc..fd3933ef 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipEvent.cs @@ -20,6 +20,8 @@ public class ClipEvent public Bindable Enabled = new(); public Bindable Length = new(); + public bool IsDefault => Format.IsDefault && Enabled.IsDefault && Length.IsDefault; + public ClipEvent(ClipEventMetadata metadata) { Module = metadata.Module; @@ -28,6 +30,7 @@ public ClipEvent(ClipEventMetadata metadata) Format.Value = metadata.DefaultFormat; Format.Default = metadata.DefaultFormat; Length.Value = metadata.DefaultLength; + Length.Default = metadata.DefaultLength; } } diff --git a/VRCOSC.Game/ChatBox/Clips/ClipState.cs b/VRCOSC.Game/ChatBox/Clips/ClipState.cs index cafa1604..2d3ecec1 100644 --- a/VRCOSC.Game/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.Game/ChatBox/Clips/ClipState.cs @@ -22,6 +22,8 @@ public class ClipState public List ModuleNames => States.Select(state => state.Item1).ToList(); public List StateNames => States.Select(state => state.Item2).ToList(); + public bool IsDefault => Format.IsDefault && Enabled.IsDefault; + public ClipState Copy(bool includeData = false) { var statesCopy = new List<(string, string)>(); diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs index a583a2e7..f8461790 100644 --- a/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/Structures/SerialisableClip.cs @@ -48,7 +48,15 @@ public SerialisableClip(Clip clip) AssociatedModules = clip.AssociatedModules.ToList(); Start = clip.Start.Value; End = clip.End.Value; - clip.States.ForEach(clipState => States.Add(new SerialisableClipState(clipState))); - clip.Events.ForEach(clipEvent => Events.Add(new SerialisableClipEvent(clipEvent))); + + clip.States.ForEach(clipState => + { + if (!clipState.IsDefault) States.Add(new SerialisableClipState(clipState)); + }); + + clip.Events.ForEach(clipEvent => + { + if (!clipEvent.IsDefault) Events.Add(new SerialisableClipEvent(clipEvent)); + }); } } From b3b9bd34b23b62b39654ce40876dfdf7491e5a85 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 14:18:26 +0100 Subject: [PATCH 45/59] Small UI tweaks --- .../SelectedClip/SelectedClipMetadataEditor.cs | 5 ++--- .../Graphics/ChatBox/Timeline/DrawableClip.cs | 3 ++- .../Graphics/ChatBox/Timeline/TimelineEditor.cs | 2 +- VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs | 14 +++++++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs index 634dcb6a..a3fbbd46 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipMetadataEditor.cs @@ -11,7 +11,6 @@ using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.Graphics.ChatBox.Metadata; using VRCOSC.Game.Graphics.Themes; -using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -65,10 +64,10 @@ private void load() null, new Drawable[] { - new VRCOSCScrollContainer + new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - ShowScrollbar = false, + ScrollbarVisible = false, ClampExtension = 5, Child = metadataFlow = new FillFlowContainer { diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs index ffd70f1f..33b05321 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/DrawableClip.cs @@ -102,10 +102,11 @@ protected override void LoadComplete() protected override bool OnMouseDown(MouseDownEvent e) { + chatBoxManager.SelectedClip.Value = Clip; + if (e.Button == MouseButton.Left) { timelineEditor.HideClipMenu(); - chatBoxManager.SelectedClip.Value = Clip; } else if (e.Button == MouseButton.Right) { diff --git a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs index 42a817f3..6bc94b4b 100644 --- a/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs +++ b/VRCOSC.Game/Graphics/ChatBox/Timeline/TimelineEditor.cs @@ -94,7 +94,7 @@ private void load() RelativePositionAxes = Axes.X, Width = 5, CornerRadius = 2, - EdgeEffect = VRCOSCEdgeEffects.BasicShadow, + EdgeEffect = VRCOSCEdgeEffects.UniformShadow, Masking = true, Child = new Box { diff --git a/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs b/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs index 0307e6b8..a5656901 100644 --- a/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs +++ b/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs @@ -15,7 +15,7 @@ public static class VRCOSCEdgeEffects public static readonly EdgeEffectParameters NoShadow = new() { Colour = new Color4(0, 0, 0, 0), - Radius = 0, + Radius = 0f, Type = EdgeEffectType.Shadow, Offset = Vector2.Zero }; @@ -28,11 +28,19 @@ public static class VRCOSCEdgeEffects Offset = new Vector2(0.0f, 1.5f) }; + public static readonly EdgeEffectParameters UniformShadow = new() + { + Colour = Color4.Black.Opacity(0.6f), + Radius = 5f, + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f) + }; + public static readonly EdgeEffectParameters DispersedShadow = new() { Colour = Color4.Black.Opacity(0.75f), - Radius = 15, + Radius = 15f, Type = EdgeEffectType.Shadow, - Offset = new Vector2(0) + Offset = new Vector2(0f) }; } From a7b11b1ea4747750fa91f279261c8cee5cf27723 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 15:59:12 +0100 Subject: [PATCH 46/59] Update .NET6 version --- VRCOSC.Desktop/VRCOSC.Desktop.csproj | 2 +- VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj | 2 +- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- VRCOSC.Modules/VRCOSC.Modules.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj index 45cf3303..7eb8b918 100644 --- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj +++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.22000.0 + net6.0-windows10.0.22621.0 Exe VRCOSC game.ico diff --git a/VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj b/VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj index 79e4b418..348067dd 100644 --- a/VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj +++ b/VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj @@ -1,7 +1,7 @@  WinExe - net6.0-windows10.0.22000.0 + net6.0-windows10.0.22621.0 false enable diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index a993813f..0bd5a8d5 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.22000.0 + net6.0-windows10.0.22621.0 enable 11 VolcanicArts.VRCOSC.SDK diff --git a/VRCOSC.Modules/VRCOSC.Modules.csproj b/VRCOSC.Modules/VRCOSC.Modules.csproj index 1df41808..c964904f 100644 --- a/VRCOSC.Modules/VRCOSC.Modules.csproj +++ b/VRCOSC.Modules/VRCOSC.Modules.csproj @@ -1,7 +1,7 @@ - net6.0-windows10.0.22000.0 + net6.0-windows10.0.22621.0 enable enable true From 2746b3691b676878e1b4d46f4d7f57927f996268 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 15:59:34 +0100 Subject: [PATCH 47/59] Fix synchronous hang on module start --- VRCOSC.Modules/Media/MediaModule.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index 848eec30..21a4d1b4 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -55,7 +55,13 @@ protected override void CreateAttributes() CreateEvent(MediaEvent.NowPlaying, "Now Playing", $@"[Now Playing] {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}", 5); } - protected override async void OnModuleStart() + protected override void OnModuleStart() + { + hookIntoMedia(); + startProcesses(); + } + + private void hookIntoMedia() => Task.Run(async () => { var result = await mediaProvider.Hook(); @@ -66,9 +72,7 @@ protected override async void OnModuleStart() } ChangeStateTo(mediaProvider.State.IsPlaying ? MediaState.Playing : MediaState.Paused); - - startProcesses(); - } + }); private void startProcesses() { From bd23e6854337add63e1c5aa884237e2d28fdb9ca Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 16:45:44 +0100 Subject: [PATCH 48/59] Add relevant state filter --- VRCOSC.Game/ChatBox/ChatBoxManager.cs | 6 +- .../ChatBox/SelectedClip/DrawableState.cs | 10 +- .../SelectedClipStateEditorContainer.cs | 95 +++++++++++++++++-- VRCOSC.Game/Modules/GameManager.cs | 2 +- VRCOSC.Game/Modules/Manager/ModuleManager.cs | 9 +- 5 files changed, 105 insertions(+), 17 deletions(-) diff --git a/VRCOSC.Game/ChatBox/ChatBoxManager.cs b/VRCOSC.Game/ChatBox/ChatBoxManager.cs index 8a46a9cc..654ccf15 100644 --- a/VRCOSC.Game/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.Game/ChatBox/ChatBoxManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using VRCOSC.Game.ChatBox.Clips; using VRCOSC.Game.ChatBox.Serialisation.V1; +using VRCOSC.Game.Modules; using VRCOSC.Game.OSC.VRChat; namespace VRCOSC.Game.ChatBox; @@ -54,13 +55,16 @@ public bool SendEnabled public int CurrentSecond => (int)Math.Floor((DateTimeOffset.Now - startTime).TotalSeconds) % (int)TimelineLength.Value.TotalSeconds; private bool sendAllowed => nextValidTime <= DateTimeOffset.Now; + public GameManager GameManager = null!; + private DateTimeOffset startTime; private DateTimeOffset nextValidTime; private bool isClear; private bool isLoaded; - public void Load(Storage storage) + public void Load(Storage storage, GameManager gameManager) { + GameManager = gameManager; serialiser = new TimelineSerialiser(storage); if (storage.Exists(@"chatbox.json")) diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs index 47552984..043cb81c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/DrawableState.cs @@ -24,11 +24,11 @@ public partial class DrawableState : Container [Resolved] private GameManager gameManager { get; set; } = null!; - private readonly ClipState clipState; + public readonly ClipState ClipState; public DrawableState(ClipState clipState) { - this.clipState = clipState; + ClipState = clipState; } [BackgroundDependencyLoader] @@ -36,7 +36,7 @@ private void load() { var stateNameList = string.Empty; - clipState.States.ForEach(pair => + ClipState.States.ForEach(pair => { var stateMetadata = chatBoxManager.StateMetadata[pair.Item1][pair.Item2]; stateNameList += gameManager.ModuleManager.GetModuleName(pair.Item1); @@ -82,7 +82,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - State = clipState.Enabled.GetBoundCopy() + State = ClipState.Enabled.GetBoundCopy() } }, new Container @@ -112,7 +112,7 @@ private void load() { RelativeSizeAxes = Axes.X, Height = 30, - Current = clipState.Format.GetBoundCopy(), + Current = ClipState.Format.GetBoundCopy(), Masking = true, CornerRadius = 5 } diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs index 26671115..253e0d63 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs @@ -2,7 +2,9 @@ // See the LICENSE file in the repository root for full license text. using System.Collections.Specialized; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,6 +13,8 @@ using osuTK; using VRCOSC.Game.ChatBox; using VRCOSC.Game.Graphics.Themes; +using VRCOSC.Game.Graphics.UI.Button; +using VRCOSC.Game.Modules.Manager; namespace VRCOSC.Game.Graphics.ChatBox.SelectedClip; @@ -20,10 +24,12 @@ public partial class SelectedClipStateEditorContainer : Container private ChatBoxManager chatBoxManager { get; set; } = null!; private Container statesTitle = null!; - private FillFlowContainer stateFlow = null!; + private FillFlowContainer stateFlow = null!; private LineSeparator separator = null!; private Container eventsTitle = null!; - private FillFlowContainer eventFlow = null!; + private FillFlowContainer eventFlow = null!; + + private Bindable showRelevantStates = new(true); [BackgroundDependencyLoader] private void load() @@ -42,7 +48,12 @@ private void load() new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + Padding = new MarginPadding + { + Horizontal = 10, + Top = 10, + Bottom = 50 + }, Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, @@ -80,7 +91,7 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Text] } }, - stateFlow = new FillFlowContainer + stateFlow = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -122,7 +133,7 @@ private void load() Colour = ThemeManager.Current[ThemeAttribute.Text] } }, - eventFlow = new FillFlowContainer + eventFlow = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -136,6 +147,51 @@ private void load() } } } + }, + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 50, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Padding = new MarginPadding(5), + Children = new Drawable[] + { + new ToggleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + State = showRelevantStates.GetBoundCopy() + } + } + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = "Show relevant states only", + Colour = ThemeManager.Current[ThemeAttribute.SubText] + } + } + } + } } }; } @@ -152,15 +208,34 @@ protected override void LoadComplete() associatedModulesOnCollectionChanged(null, null); } }, true); + + ((ModuleManager)chatBoxManager.GameManager.ModuleManager).OnModuleEnabledChanged += filterFlows; + showRelevantStates.BindValueChanged(_ => filterFlows(), true); } - private void associatedModulesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) + private void filterFlows() { - // TODO Add button to filter states/events of modules where the module is relevant (enabled) or show all states/events - //gameManager.ModuleManager.GetEnabledModuleNames(); + if (showRelevantStates.Value) + { + var enabledModuleNames = chatBoxManager.GameManager.ModuleManager.GetEnabledModuleNames().Where(moduleName => chatBoxManager.SelectedClip.Value!.AssociatedModules.Contains(moduleName)).ToList(); + enabledModuleNames.Sort(); - // Get module states of all associated modules, then filter states that contain all enabled associated modules + stateFlow.ForEach(drawableState => + { + var sortedClipModuleNames = drawableState.ClipState.ModuleNames; + sortedClipModuleNames.Sort(); + + drawableState.Alpha = enabledModuleNames.SequenceEqual(sortedClipModuleNames) ? 1 : 0; + }); + } + else + { + stateFlow.ForEach(drawableState => drawableState.Alpha = 1); + } + } + private void associatedModulesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) + { stateFlow.Clear(); eventFlow.Clear(); @@ -197,5 +272,7 @@ private void associatedModulesOnCollectionChanged(object? sender, NotifyCollecti statesTitle.Alpha = stateFlow.Children.Count == 0 ? 0 : 1; eventsTitle.Alpha = separator.Alpha = eventFlow.Children.Count == 0 ? 0 : 1; + + filterFlows(); } } diff --git a/VRCOSC.Game/Modules/GameManager.cs b/VRCOSC.Game/Modules/GameManager.cs index 6043950c..075a3110 100644 --- a/VRCOSC.Game/Modules/GameManager.cs +++ b/VRCOSC.Game/Modules/GameManager.cs @@ -96,7 +96,7 @@ private void load() setupModules(); - chatBoxManager.Load(storage); + chatBoxManager.Load(storage, this); } private void setupModules() diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index b9261ac5..0648aa20 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -21,6 +21,8 @@ public sealed class ModuleManager : IModuleManager private readonly SortedList modules = new(); private IModuleSerialiser? serialiser; + public Action? OnModuleEnabledChanged; + public void AddSource(IModuleSource source) => sources.Add(source); public bool RemoveSource(IModuleSource source) => sources.Remove(source); public void SetSerialiser(IModuleSerialiser serialiser) => this.serialiser = serialiser; @@ -56,7 +58,12 @@ public void Load() { module.Load(); serialiser?.Deserialise(module); - module.Enabled.BindValueChanged(_ => serialiser?.Serialise(module)); + + module.Enabled.BindValueChanged(_ => + { + OnModuleEnabledChanged?.Invoke(); + serialiser?.Serialise(module); + }); } } From 56c1b1aad330564f8bddcc0ce2e1bdf16f414e32 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 17:28:57 +0100 Subject: [PATCH 49/59] Add clarification --- .../ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs index 253e0d63..6d5fd04c 100644 --- a/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs +++ b/VRCOSC.Game/Graphics/ChatBox/SelectedClip/SelectedClipStateEditorContainer.cs @@ -186,7 +186,7 @@ private void load() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = "Show relevant states only", + Text = "Show relevant states only (Based on enabled modules)", Colour = ThemeManager.Current[ThemeAttribute.SubText] } } From f3de6d822e5e5f683326d5b20325ccadd35f8ec7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 17:29:16 +0100 Subject: [PATCH 50/59] Add more variables to MediaModule --- .../Providers/Media/WindowsMediaProvider.cs | 8 ++++++++ VRCOSC.Modules/Media/MediaModule.cs | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs index 83816057..36b16ee0 100644 --- a/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs +++ b/VRCOSC.Game/Providers/Media/WindowsMediaProvider.cs @@ -67,6 +67,10 @@ private void onAnyMediaPropertyChanged(GlobalSystemMediaTransportControlsSession State.Title = args.Title; State.Artist = args.Artist; + State.TrackNumber = args.TrackNumber; + State.AlbumTitle = args.AlbumTitle; + State.AlbumArtist = args.AlbumArtist; + State.AlbumTrackCount = args.AlbumTrackCount; OnTrackChange?.Invoke(); } @@ -124,6 +128,10 @@ public class MediaState public string? ProcessId; public string Title = string.Empty; public string Artist = string.Empty; + public int TrackNumber; + public string AlbumTitle = string.Empty; + public string AlbumArtist = string.Empty; + public int AlbumTrackCount; public MediaPlaybackAutoRepeatMode RepeatMode; public bool IsShuffle; public GlobalSystemMediaTransportControlsSessionPlaybackStatus Status; diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index 21a4d1b4..fbd5ec7f 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -45,6 +45,10 @@ protected override void CreateAttributes() CreateVariable(MediaVariable.Title, @"Title", @"title"); CreateVariable(MediaVariable.Artist, @"Artist", @"artist"); + CreateVariable(MediaVariable.TrackNumber, @"Track Number", @"tracknumber"); + CreateVariable(MediaVariable.AlbumTitle, @"Album Title", @"albumtitle"); + CreateVariable(MediaVariable.AlbumArtist, @"Album Artist", @"albumartist"); + CreateVariable(MediaVariable.AlbumTrackCount, @"Album Track Count", @"albumtrackcount"); CreateVariable(MediaVariable.Time, @"Time", @"time"); CreateVariable(MediaVariable.Duration, @"Duration", @"duration"); CreateVariable(MediaVariable.Volume, @"Volume", @"volume"); @@ -108,6 +112,10 @@ private void updateVariables() { SetVariableValue(MediaVariable.Title, mediaProvider.State.Title); SetVariableValue(MediaVariable.Artist, mediaProvider.State.Artist); + SetVariableValue(MediaVariable.TrackNumber, mediaProvider.State.TrackNumber.ToString()); + SetVariableValue(MediaVariable.AlbumTitle, mediaProvider.State.AlbumTitle); + SetVariableValue(MediaVariable.AlbumArtist, mediaProvider.State.AlbumArtist); + SetVariableValue(MediaVariable.AlbumTrackCount, mediaProvider.State.AlbumTrackCount.ToString()); SetVariableValue(MediaVariable.Time, mediaProvider.State.Position?.Position.ToString(@"mm\:ss")); SetVariableValue(MediaVariable.Duration, mediaProvider.State.Position?.EndTime.ToString(@"mm\:ss")); SetVariableValue(MediaVariable.Volume, (mediaProvider.State.Volume * 100).ToString("##0")); @@ -228,7 +236,11 @@ private enum MediaVariable Artist, Time, Duration, - Volume + Volume, + TrackNumber, + AlbumTitle, + AlbumArtist, + AlbumTrackCount } private enum MediaParameter From 64b8794b553c041e3259dd83e118178f6e8bb524 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 17:30:26 +0100 Subject: [PATCH 51/59] Add condition string to WeatherModule --- VRCOSC.Modules/Weather/WeatherModule.cs | 5 ++++- VRCOSC.Modules/Weather/WeatherProvider.cs | 13 ++++++++++++- VRCOSC.Modules/Weather/WeatherResponse.cs | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index e577ab31..15ae60e6 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -27,6 +27,7 @@ protected override void CreateAttributes() CreateVariable(WeatherVariable.TempC, @"Temp C", @"tempc"); CreateVariable(WeatherVariable.TempF, @"Temp F", @"tempf"); CreateVariable(WeatherVariable.Humidity, @"Humidity", @"humidity"); + CreateVariable(WeatherVariable.Condition, @"Condition", @"condition"); CreateState(WeatherState.Default, @"Default", $@"Local Weather {GetVariableFormat(WeatherVariable.TempC)}C - {GetVariableFormat(WeatherVariable.TempF)}F"); } @@ -81,6 +82,7 @@ private void updateParameters() SetVariableValue(WeatherVariable.TempC, currentWeather.TempC.ToString("0.0")); SetVariableValue(WeatherVariable.TempF, currentWeather.TempF.ToString("0.0")); SetVariableValue(WeatherVariable.Humidity, currentWeather.Humidity.ToString()); + SetVariableValue(WeatherVariable.Condition, currentWeather.ConditionString); } private int convertedWeatherCode => currentWeather!.Condition.Code switch @@ -155,6 +157,7 @@ private enum WeatherVariable { TempC, TempF, - Humidity + Humidity, + Condition } } diff --git a/VRCOSC.Modules/Weather/WeatherProvider.cs b/VRCOSC.Modules/Weather/WeatherProvider.cs index 40a5fbff..384c677a 100644 --- a/VRCOSC.Modules/Weather/WeatherProvider.cs +++ b/VRCOSC.Modules/Weather/WeatherProvider.cs @@ -7,6 +7,8 @@ namespace VRCOSC.Modules.Weather; public class WeatherProvider { + private const string condition_url = "https://www.weatherapi.com/docs/weather_conditions.json"; + private readonly HttpClient httpClient = new(); private readonly string apiKey; @@ -20,6 +22,15 @@ public WeatherProvider(string apiKey) var uri = $"https://api.weatherapi.com/v1/current.json?key={apiKey}&q={postcode}"; var data = await httpClient.GetAsync(new Uri(uri)); var responseString = await data.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseString)?.Current; + var weatherResponse = JsonConvert.DeserializeObject(responseString)?.Current; + + if (weatherResponse is null) return null; + + using var client = new HttpClient(); + var response = await client.GetAsync(condition_url); + var conditionContent = await response.Content.ReadAsStringAsync(); + weatherResponse.ConditionString = JsonConvert.DeserializeObject>(conditionContent)?.Single(condition => condition.Code == weatherResponse.Condition.Code).Day ?? string.Empty; + + return weatherResponse; } } diff --git a/VRCOSC.Modules/Weather/WeatherResponse.cs b/VRCOSC.Modules/Weather/WeatherResponse.cs index 1900aea0..602dae4c 100644 --- a/VRCOSC.Modules/Weather/WeatherResponse.cs +++ b/VRCOSC.Modules/Weather/WeatherResponse.cs @@ -24,6 +24,9 @@ public class Weather [JsonProperty("condition")] public Condition Condition = null!; + + [JsonIgnore] + public string ConditionString = null!; } public class Condition @@ -31,3 +34,18 @@ public class Condition [JsonProperty("code")] public int Code; } + +public class WeatherCondition +{ + [JsonProperty("code")] + public int Code; + + [JsonProperty("day")] + public string Day = null!; + + [JsonProperty("night")] + public string Night = null!; + + [JsonProperty("icon")] + public int Icon; +} From d30f619c4fc9c7fb7c72d75bad1d137386f5ffe0 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 17:47:40 +0100 Subject: [PATCH 52/59] Enable settings menu if a module has settings or parameters --- VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs index 9f5f22c2..3af6f3fe 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs @@ -152,7 +152,7 @@ public ModuleCard(Module module) Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - Alpha = Module.HasSettings ? 1 : 0.5f, + Alpha = Module.HasSettings || Module.HasParameters ? 1 : 0.5f, Child = new IconButton { Anchor = Anchor.Centre, @@ -163,7 +163,7 @@ public ModuleCard(Module module) CornerRadius = 5, Action = () => editingModule.Value = Module, BackgroundColour = ThemeManager.Current[ThemeAttribute.Light], - Enabled = { Value = Module.HasSettings } + Enabled = { Value = Module.HasSettings || Module.HasParameters } } } } From 82b6348a6c06f1f005db693acba5b9beaa4b0a75 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:22:44 +0100 Subject: [PATCH 53/59] Add normalised bounds and smoothing filter to all HeartRate modules --- VRCOSC.Game/Modules/Manager/ModuleManager.cs | 5 ++ VRCOSC.Game/Modules/Module.cs | 3 ++ VRCOSC.Modules/Heartrate/HeartRateModule.cs | 55 ++++++++++++++++---- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/VRCOSC.Game/Modules/Manager/ModuleManager.cs b/VRCOSC.Game/Modules/Manager/ModuleManager.cs index 0648aa20..667939a8 100644 --- a/VRCOSC.Game/Modules/Manager/ModuleManager.cs +++ b/VRCOSC.Game/Modules/Manager/ModuleManager.cs @@ -94,6 +94,11 @@ public void Start() public void Update() { scheduler.Update(); + + foreach (var module in modules) + { + module.FrameUpdate(); + } } public void Stop() diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index 860df821..786eac42 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -142,6 +142,8 @@ internal void Start() if (ShouldUpdateImmediately) OnModuleUpdate(); } + internal void FrameUpdate() => OnFrameUpdate(); + internal void Stop() { if (!IsEnabled) return; @@ -155,6 +157,7 @@ internal void Stop() protected virtual void OnModuleStart() { } protected virtual void OnModuleUpdate() { } + protected virtual void OnFrameUpdate() { } protected virtual void OnModuleStop() { } protected virtual void OnAvatarChange() { } diff --git a/VRCOSC.Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Modules/Heartrate/HeartRateModule.cs index c7719735..b8aa94a0 100644 --- a/VRCOSC.Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HeartRateModule.cs @@ -13,10 +13,12 @@ public abstract class HeartRateModule : ChatBoxModule public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Heartrate"; public override ModuleType Type => ModuleType.Health; - protected override TimeSpan DeltaUpdate => TimeSpan.FromSeconds(2); protected HeartRateProvider? HeartRateProvider; - private int lastHeartrate; + private int currentHeartrate; + private int targetHeartrate; + private TimeSpan targetInterval; + private DateTimeOffset lastIntervalUpdate; private DateTimeOffset lastHeartrateTime; private int connectionCount; @@ -26,8 +28,13 @@ public abstract class HeartRateModule : ChatBoxModule protected override void CreateAttributes() { + CreateSetting(HeartrateSetting.Smoothed, @"Smoothed", @"Whether the heartrate value should jump to the correct value, or smoothly climb over a set period", false); + CreateSetting(HeartrateSetting.SmoothingLength, @"Smoothing Length", @"The length of time (in milliseconds) the heartrate value should take to reach the correct value", 1000, () => GetSetting(HeartrateSetting.Smoothed)); + CreateSetting(HeartrateSetting.NormalisedLowerbound, @"Normalised Lowerbound", @"The lower bound BPM the normalised parameter should use", 0); + CreateSetting(HeartrateSetting.NormalisedUpperbound, @"Normalised Upperbound", @"The upper bound BPM the normalised parameter should use", 240); + CreateParameter(HeartrateParameter.Enabled, ParameterMode.Write, "VRCOSC/Heartrate/Enabled", "Enabled", "Whether this module is attempting to emit values"); - CreateParameter(HeartrateParameter.Normalised, ParameterMode.Write, "VRCOSC/Heartrate/Normalised", "Normalised", "The heartrate value normalised to 240bpm"); + CreateParameter(HeartrateParameter.Normalised, ParameterMode.Write, "VRCOSC/Heartrate/Normalised", "Normalised", "The heartrate value normalised to the set bounds"); CreateParameter(HeartrateParameter.Units, ParameterMode.Write, "VRCOSC/Heartrate/Units", "Units", "The units digit 0-9 mapped to a float"); CreateParameter(HeartrateParameter.Tens, ParameterMode.Write, "VRCOSC/Heartrate/Tens", "Tens", "The tens digit 0-9 mapped to a float"); CreateParameter(HeartrateParameter.Hundreds, ParameterMode.Write, "VRCOSC/Heartrate/Hundreds", "Hundreds", "The hundreds digit 0-9 mapped to a float"); @@ -41,6 +48,9 @@ protected override void OnModuleStart() { attemptConnection(); lastHeartrateTime = DateTimeOffset.Now - heartrate_timeout; + lastIntervalUpdate = DateTimeOffset.Now; + currentHeartrate = 0; + targetHeartrate = 0; ChangeStateTo(HeartrateState.Default); } @@ -80,22 +90,41 @@ protected override void OnModuleStop() SendParameter(HeartrateParameter.Enabled, false); } - protected override void OnModuleUpdate() + protected override void OnFrameUpdate() { if (!isReceiving) SendParameter(HeartrateParameter.Enabled, false); - SetVariableValue(HeartrateVariable.Heartrate, lastHeartrate.ToString()); + if (GetSetting(HeartrateSetting.Smoothed)) + { + if (lastIntervalUpdate + targetInterval <= DateTimeOffset.Now) + { + lastIntervalUpdate = DateTimeOffset.Now; + currentHeartrate += Math.Sign(targetHeartrate - currentHeartrate); + } + } + else + { + currentHeartrate = targetHeartrate; + } + + SetVariableValue(HeartrateVariable.Heartrate, currentHeartrate.ToString()); + sendParameters(); } protected virtual void HandleHeartRateUpdate(int heartrate) { - lastHeartrate = heartrate; + targetHeartrate = heartrate; lastHeartrateTime = DateTimeOffset.Now; - - var normalisedHeartRate = heartrate / 240.0f; - var individualValues = toDigitArray(heartrate, 3); + targetInterval = TimeSpan.FromTicks(TimeSpan.FromMilliseconds(GetSetting(HeartrateSetting.SmoothingLength)).Ticks / Math.Abs(currentHeartrate - targetHeartrate)); SendParameter(HeartrateParameter.Enabled, true); + } + + private void sendParameters() + { + var normalisedHeartRate = Map(currentHeartrate, GetSetting(HeartrateSetting.NormalisedLowerbound), GetSetting(HeartrateSetting.NormalisedUpperbound), 0, 1); + var individualValues = toDigitArray(currentHeartrate, 3); + SendParameter(HeartrateParameter.Normalised, normalisedHeartRate); SendParameter(HeartrateParameter.Units, individualValues[2] / 10f); SendParameter(HeartrateParameter.Tens, individualValues[1] / 10f); @@ -107,6 +136,14 @@ private static int[] toDigitArray(int num, int totalWidth) return num.ToString().PadLeft(totalWidth, '0').Select(digit => int.Parse(digit.ToString())).ToArray(); } + protected enum HeartrateSetting + { + NormalisedLowerbound, + NormalisedUpperbound, + Smoothed, + SmoothingLength + } + protected enum HeartrateParameter { Enabled, From 9c0f9a659368b3c4e6264053bb6f99da27746745 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:55:44 +0100 Subject: [PATCH 54/59] Add weather condition to default state format --- VRCOSC.Modules/Weather/WeatherModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index 15ae60e6..d37d6d24 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -29,7 +29,7 @@ protected override void CreateAttributes() CreateVariable(WeatherVariable.Humidity, @"Humidity", @"humidity"); CreateVariable(WeatherVariable.Condition, @"Condition", @"condition"); - CreateState(WeatherState.Default, @"Default", $@"Local Weather {GetVariableFormat(WeatherVariable.TempC)}C - {GetVariableFormat(WeatherVariable.TempF)}F"); + CreateState(WeatherState.Default, @"Default", $@"Local Weather {GetVariableFormat(WeatherVariable.Condition)} {GetVariableFormat(WeatherVariable.TempC)}C - {GetVariableFormat(WeatherVariable.TempF)}F"); } protected override void OnModuleStart() From 022557be12d88f48ce1fa7d239266b0d20023578 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:55:58 +0100 Subject: [PATCH 55/59] Fix Media NowPlaying event formatting --- VRCOSC.Modules/Media/MediaModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Modules/Media/MediaModule.cs b/VRCOSC.Modules/Media/MediaModule.cs index fbd5ec7f..816054ad 100644 --- a/VRCOSC.Modules/Media/MediaModule.cs +++ b/VRCOSC.Modules/Media/MediaModule.cs @@ -56,7 +56,7 @@ protected override void CreateAttributes() CreateState(MediaState.Playing, "Playing", $@"[{GetVariableFormat(MediaVariable.Time)}/{GetVariableFormat(MediaVariable.Duration)}] Now Playing: {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}"); CreateState(MediaState.Paused, "Paused", @"[Paused]"); - CreateEvent(MediaEvent.NowPlaying, "Now Playing", $@"[Now Playing] {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}", 5); + CreateEvent(MediaEvent.NowPlaying, "Now Playing", $@"[Now Playing] {GetVariableFormat(MediaVariable.Artist)} - {GetVariableFormat(MediaVariable.Title)}", 5); } protected override void OnModuleStart() From 1dd05c33c8ea2e1528a78caca0bbc5796b03a81b Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:56:11 +0100 Subject: [PATCH 56/59] Fix HeartRateModule DivideByZero exception --- VRCOSC.Modules/Heartrate/HeartRateModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Modules/Heartrate/HeartRateModule.cs index b8aa94a0..b85647f5 100644 --- a/VRCOSC.Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Modules/Heartrate/HeartRateModule.cs @@ -115,7 +115,7 @@ protected virtual void HandleHeartRateUpdate(int heartrate) { targetHeartrate = heartrate; lastHeartrateTime = DateTimeOffset.Now; - targetInterval = TimeSpan.FromTicks(TimeSpan.FromMilliseconds(GetSetting(HeartrateSetting.SmoothingLength)).Ticks / Math.Abs(currentHeartrate - targetHeartrate)); + targetInterval = currentHeartrate != targetHeartrate ? TimeSpan.FromTicks(TimeSpan.FromMilliseconds(GetSetting(HeartrateSetting.SmoothingLength)).Ticks / Math.Abs(currentHeartrate - targetHeartrate)) : TimeSpan.Zero; SendParameter(HeartrateParameter.Enabled, true); } From eba8043be412566dd51047d4c3c58dc1f3c4c3ee Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:56:30 +0100 Subject: [PATCH 57/59] Fix excessive scheduling when not on the Modules tab --- .../Graphics/ModuleRun/ParameterDisplay.cs | 53 +++++++++++-------- .../Graphics/ModuleRun/TerminalContainer.cs | 25 ++++++--- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs b/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs index f9b71725..508dd474 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs @@ -4,16 +4,21 @@ using System.Collections.Generic; using System.Linq; 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.Sprites; +using VRCOSC.Game.Graphics.TabBar; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; public sealed partial class ParameterDisplay : Container { + [Resolved] + private Bindable selectedTab { get; set; } = null!; + public string Title { get; init; } = null!; private readonly SortedDictionary parameterDict = new(); @@ -88,32 +93,36 @@ public void ClearContent() parameterDict.Clear(); } - public void AddEntry(string key, object value) => Schedule(() => + public void AddEntry(string key, object value) { - var valueStr = value.ToString() ?? "Invalid Object"; + if (selectedTab.Value != Tab.Modules) return; - if (parameterDict.ContainsKey(key)) - { - var existingEntry = parameterDict[key]; - existingEntry.Value.Value = valueStr; - } - else + Schedule(() => { - var newEntry = new ParameterEntry + var valueStr = value.ToString() ?? "Invalid Object"; + + if (parameterDict.TryGetValue(key, out var existingEntry)) + { + existingEntry.Value.Value = valueStr; + } + else { - Key = key, - Value = { Value = valueStr } - }; + var newEntry = new ParameterEntry + { + Key = key, + Value = { Value = valueStr } + }; - parameterDict.Add(key, newEntry); - parameterFlow.Add(newEntry); + parameterDict.Add(key, newEntry); + parameterFlow.Add(newEntry); - parameterDict.ForEach(pair => - { - var (_, entry) = pair; - var positionOfEntry = parameterDict.Values.ToList().IndexOf(entry); - parameterFlow.SetLayoutPosition(entry, parameterDict.Count - positionOfEntry); - }); - } - }); + parameterDict.ForEach(pair => + { + var (_, entry) = pair; + var positionOfEntry = parameterDict.Values.ToList().IndexOf(entry); + parameterFlow.SetLayoutPosition(entry, parameterDict.Count - positionOfEntry); + }); + } + }); + } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs index 6322bb32..268f9368 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs @@ -2,11 +2,14 @@ // See the LICENSE file in the repository root for full license text. using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; +using VRCOSC.Game.Graphics.TabBar; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -19,6 +22,9 @@ public sealed partial class TerminalContainer : Container private readonly DrawablePool terminalEntryPool = new(75); + [Resolved] + private Bindable selectedTab { get; set; } = null!; + public TerminalContainer() { InternalChildren = new Drawable[] @@ -83,12 +89,17 @@ protected override void UpdateAfterChildren() terminalScroll.ScrollToEnd(); } - private void log(string text) => Schedule(() => + private void log(string text) { - var entry = terminalEntryPool.Get(); - entry.Text = $"[{DateTime.Now:HH:mm:ss}] {text}"; - Add(entry); - entry.Hide(); - entry.Show(); - }); + if (selectedTab.Value != Tab.Modules) return; + + Schedule(() => + { + var entry = terminalEntryPool.Get(); + entry.Text = $"[{DateTime.Now:HH:mm:ss}] {text}"; + Add(entry); + entry.Hide(); + entry.Show(); + }); + } } From 907d2b3b6bbc0c0917a2ce24c03d6520e705c9d7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 18:58:25 +0100 Subject: [PATCH 58/59] Return offscreen terminal logging with correct time calculation --- VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs index 268f9368..0be13d2b 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs @@ -2,14 +2,11 @@ // See the LICENSE file in the repository root for full license text. using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; -using VRCOSC.Game.Graphics.TabBar; using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -22,9 +19,6 @@ public sealed partial class TerminalContainer : Container private readonly DrawablePool terminalEntryPool = new(75); - [Resolved] - private Bindable selectedTab { get; set; } = null!; - public TerminalContainer() { InternalChildren = new Drawable[] @@ -91,12 +85,12 @@ protected override void UpdateAfterChildren() private void log(string text) { - if (selectedTab.Value != Tab.Modules) return; + var dateTimeText = $"[{DateTime.Now:HH:mm:ss}] {text}"; Schedule(() => { var entry = terminalEntryPool.Get(); - entry.Text = $"[{DateTime.Now:HH:mm:ss}] {text}"; + entry.Text = dateTimeText; Add(entry); entry.Hide(); entry.Show(); From 9f1dc8c101f5766859328fafdaa739bdee734888 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 23 Apr 2023 20:23:14 +0100 Subject: [PATCH 59/59] Version bump --- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- VRCOSC.Templates/VRCOSC.Templates.csproj | 2 +- .../template-default/TemplateModule/TemplateModule.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index 0bd5a8d5..b173e30a 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -4,7 +4,7 @@ enable 11 VolcanicArts.VRCOSC.SDK - 2023.306.1 + 2023.423.0 VRCOSC SDK VolcanicArts SDK for creating custom modules with VRCOSC diff --git a/VRCOSC.Templates/VRCOSC.Templates.csproj b/VRCOSC.Templates/VRCOSC.Templates.csproj index df4f4680..52021bfd 100644 --- a/VRCOSC.Templates/VRCOSC.Templates.csproj +++ b/VRCOSC.Templates/VRCOSC.Templates.csproj @@ -11,7 +11,7 @@ true NU5128 - 2023.306.0 + 2023.423.0 VolcanicArts https://github.com/VolcanicArts/VRCOSC https://github.com/VolcanicArts/VRCOSC diff --git a/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj b/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj index 749868c2..468b3d7b 100644 --- a/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj +++ b/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj @@ -7,7 +7,7 @@ - +