diff --git a/ProjectObsidian/Components/Common UI/Button Interactions/ButtonAttachComponent.cs b/ProjectObsidian/Components/Common UI/Button Interactions/ButtonAttachComponent.cs new file mode 100644 index 0000000..fc6e8af --- /dev/null +++ b/ProjectObsidian/Components/Common UI/Button Interactions/ButtonAttachComponent.cs @@ -0,0 +1,43 @@ +using System; +using FrooxEngine; +using FrooxEngine.Undo; + +namespace Obsidian; + +[Category(new string[] { "Obsidian/Common UI/Button Interactions" })] +public class ButtonAttachComponent : Component, IButtonPressReceiver, IComponent, IComponentBase, IDestroyable, IWorker, IWorldElement, IUpdatable, IChangeable, IAudioUpdatable, IInitializable, ILinkable +{ + public readonly SyncRef TargetSlot; + + public readonly SyncType ComponentType; + + public readonly Sync Undoable; + + protected override void OnAttach() + { + base.OnAwake(); + Undoable.Value = true; + } + + public void Pressed(IButton button, ButtonEventData eventData) + { + Slot target = TargetSlot.Target; + Type componentType = ComponentType.Value; + if (target != null && componentType != null && componentType.ContainsGenericParameters == false) + { + var comp = target.AttachComponent(componentType); + if (Undoable) + { + comp.CreateSpawnUndoPoint(); + } + } + } + + public void Pressing(IButton button, ButtonEventData eventData) + { + } + + public void Released(IButton button, ButtonEventData eventData) + { + } +} \ No newline at end of file diff --git a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentData.cs b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentData.cs index 14c3427..99be453 100644 --- a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentData.cs +++ b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentData.cs @@ -11,21 +11,37 @@ public class ComponentData public Component component; + private Type _componentType; + public bool Submitted { get; private set; } public string MainName { get { - return component.Name; + return component?.Name ?? _componentType.Name; } } + public string uniqueId; + public int MemberCount => members.Count; + public Type ComponentType => component?.GetType() ?? _componentType; + + public bool IsGenericType => ComponentType.IsGenericType; + + public Type GenericTypeDefinition => IsGenericType ? ComponentType.GetGenericTypeDefinition() : null; + public ComponentData(Component component) { this.component = component; + _componentType = component.GetType(); + } + + public ComponentData(Type type) + { + this._componentType = type; } public void MarkSubmitted() @@ -87,12 +103,9 @@ public bool MatchesSearchParameters(List optionalTerms, List req public bool MatchesTerm(string term) { - if (component != null) + if (ContainsTerm(MainName, term)) { - if (ContainsTerm(component.Name, term)) - { - return true; - } + return true; } return false; } @@ -108,6 +121,6 @@ private static bool ContainsTerm(string str, string term) public override string ToString() { - return $"Name: {MainName}, ReferenceID: {component.ReferenceID}, Members: {members.Count}"; + return $"Name: {MainName}, UniqueID: {uniqueId}, MemberCount: {MemberCount}"; } } \ No newline at end of file diff --git a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentDataFeedItem.cs b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentDataFeedItem.cs index eae9470..8367cf3 100644 --- a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentDataFeedItem.cs +++ b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentDataFeedItem.cs @@ -11,7 +11,7 @@ public class ComponentDataFeedItem : DataFeedItem public ComponentDataFeedItem(ComponentData componentData) { - InitBase(componentData.component.ReferenceID.ToString(), null, null, componentData.MainName); + InitBase(componentData.uniqueId, null, null, componentData.MainName); Data = componentData; foreach (ISyncMember member in componentData.members) { diff --git a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeed.cs b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeed.cs index df1dad4..8948db9 100644 --- a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeed.cs +++ b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeed.cs @@ -1,11 +1,20 @@ using System; using System.Collections.Generic; +using System.Linq; using Elements.Core; using FrooxEngine; using SkyFrost.Base; namespace Obsidian; +// This feed has two functions. + +// If TargetSlot has a reference, it returns the components on that slot, optionally also returning components on children slots (IncludeChildrenSlots bool) +// It also returns the sync members (fields, lists etc) as DataFeedEntity + +// If TargetSlot is null, it returns the components from the component library, which includes the categories (DataFeedCategoryItem) +// When enumerating the component library, the Component reference on the ComponentDataItemInterface will be null and there will be no members + [Category(new string[] { "Obsidian/Radiant UI/Data Feeds/Feeds" })] public class ComponentsDataFeed : Component, IDataFeedComponent, IDataFeed, IWorldElement { @@ -19,23 +28,32 @@ public class ComponentsDataFeed : Component, IDataFeedComponent, IDataFeed, IWor private Slot _lastSlot = null; - private void AddComponent(Component c) + private static HashSet _componentTypes = new(); + + private static bool SearchStringValid(string str) + { + return !string.IsNullOrWhiteSpace(str) && str.Length >= 3; + } + + private void OnSlotComponentAdded(Component c) { // If local elements are written to synced fields it can cause exceptions and crashes if (c.IsLocalElement) return; foreach (KeyValuePair updateHandler in _updateHandlers) { - var data = updateHandler.Value.RegisterComponent(c); - foreach (ISyncMember syncMember in data.component.SyncMembers) + var result = updateHandler.Value.AddComponent(c); + foreach (ISyncMember syncMember in result.data.component.SyncMembers) { - if (syncMember.IsLocalElement) continue; - data.AddMember(syncMember); + if (FilterMember(syncMember)) + { + result.data.AddMember(syncMember); + } } - ProcessUpdate(updateHandler.Key, data); + ProcessUpdate(updateHandler.Key, result.data); } } - private void RemoveComponent(Component c) + private void OnSlotComponentRemoved(Component c) { foreach (KeyValuePair updateHandler in _updateHandlers) { @@ -53,22 +71,28 @@ private void Update() } } + private bool FilterMember(ISyncMember member) + { + if (member.IsLocalElement) return false; + return true; + } + private void ProcessUpdate(SearchPhraseFeedUpdateHandler handler, ComponentData data) { bool flag = true; if (!string.IsNullOrEmpty(handler.searchPhrase)) { - List list = Pool.BorrowList(); - List list2 = Pool.BorrowList(); - List list3 = Pool.BorrowList(); - SearchQueryParser.Parse(handler.searchPhrase, list, list2, list3); - if (!data.MatchesSearchParameters(list, list2, list3)) + List optionalTerms = Pool.BorrowList(); + List requiredTerms = Pool.BorrowList(); + List excludedTerms = Pool.BorrowList(); + SearchQueryParser.Parse(handler.searchPhrase, optionalTerms, requiredTerms, excludedTerms); + if (!data.MatchesSearchParameters(optionalTerms, requiredTerms, excludedTerms)) { flag = false; } - Pool.Return(ref list); - Pool.Return(ref list2); - Pool.Return(ref list3); + Pool.Return(ref optionalTerms); + Pool.Return(ref requiredTerms); + Pool.Return(ref excludedTerms); } if (!flag) { @@ -86,14 +110,20 @@ private void ProcessUpdate(SearchPhraseFeedUpdateHandler handler, ComponentData private void Subscribe(Slot s) { - s.ComponentAdded += AddComponent; - s.ComponentRemoved += RemoveComponent; + s.ComponentAdded += OnSlotComponentAdded; + s.ComponentRemoved += OnSlotComponentRemoved; } private void Unsubscribe(Slot s) { - s.ComponentAdded -= AddComponent; - s.ComponentRemoved -= RemoveComponent; + s.ComponentAdded -= OnSlotComponentAdded; + s.ComponentRemoved -= OnSlotComponentRemoved; + } + + protected override void OnAwake() + { + base.OnAwake(); + _lastSlot = TargetSlot.Target; } protected override void OnChanges() @@ -156,17 +186,53 @@ protected override void OnPrepareDestroy() } } - public async IAsyncEnumerable Enumerate(IReadOnlyList path, IReadOnlyList groupKeys, string searchPhrase, object viewData) + private void GetAllTypes(HashSet allTypes, CategoryNode categoryNode) { - if (path != null && path.Count > 0) + foreach (var elem in categoryNode.Elements) { - yield break; + allTypes.Add(elem); } - if (groupKeys != null && groupKeys.Count > 0) + foreach (var subCat in categoryNode.Subcategories) + { + GetAllTypes(allTypes, subCat); + } + } + + private IEnumerable EnumerateAllTypes(CategoryNode categoryNode) + { + foreach (var elem in categoryNode.Elements) + { + yield return elem; + } + foreach (var subCat in categoryNode.Subcategories) + { + foreach(var elem2 in EnumerateAllTypes(subCat)) + { + yield return elem2; + } + } + } + + private string GetCategoryKey(CategoryNode categoryNode) + { + return categoryNode.Name; + } + + private DataFeedCategory GenerateCategory(string key, IReadOnlyList path) + { + DataFeedCategory dataFeedCategory = new DataFeedCategory(); + // random icon + dataFeedCategory.InitBase(key, path, null, key, OfficialAssets.Graphics.Icons.Gizmo.TransformLocal); + return dataFeedCategory; + } + + public async IAsyncEnumerable Enumerate(IReadOnlyList path, IReadOnlyList groupKeys, string searchPhrase, object viewData) + { + if (TargetSlot.Target != null && (path != null && path.Count > 0)) { yield break; } - if (TargetSlot.Target == null) + if (groupKeys != null && groupKeys.Count > 0) { yield break; } @@ -175,16 +241,77 @@ public async IAsyncEnumerable Enumerate(IReadOnlyList path componentDataFeedData.Clear(); searchPhrase = searchPhrase?.Trim(); - var components = IncludeChildrenSlots ? TargetSlot.Target.GetComponentsInChildren() : TargetSlot.Target.GetComponents(); - foreach (Component allComponent in components) + if (TargetSlot.Target == null) + { + var lib = WorkerInitializer.ComponentLibrary; + if (path != null && path.Count > 0) + { + var catNode = lib; + foreach (var str in path) + { + var subCat = catNode.Subcategories.FirstOrDefault(x => x.Name == str); + if (subCat != null) + { + catNode = subCat; + } + else + { + yield break; + } + } + foreach (var subCat2 in catNode.Subcategories) + { + yield return GenerateCategory(GetCategoryKey(subCat2), path); + } + if (SearchStringValid(searchPhrase)) + { + foreach (var elem in EnumerateAllTypes(catNode)) + { + componentDataFeedData.AddComponentType(elem); + } + } + else + { + foreach (var elem in catNode.Elements) + { + componentDataFeedData.AddComponentType(elem); + } + } + } + else + { + if (_componentTypes.Count == 0) + { + GetAllTypes(_componentTypes, lib); + } + foreach (var subCat in lib.Subcategories) + { + yield return GenerateCategory(GetCategoryKey(subCat), path); + } + if (SearchStringValid(searchPhrase)) + { + foreach (var elem in _componentTypes) + { + componentDataFeedData.AddComponentType(elem); + } + } + } + } + else { - // If local elements are written to synced fields it can cause exceptions and crashes - if (allComponent.IsLocalElement) continue; - componentDataFeedData.RegisterComponent(allComponent); - foreach (ISyncMember syncMember in allComponent.SyncMembers) + var components = IncludeChildrenSlots ? TargetSlot.Target.GetComponentsInChildren() : TargetSlot.Target.GetComponents(); + foreach (Component allComponent in components) { - if (syncMember.IsLocalElement) continue; - componentDataFeedData.AddMember(syncMember); + // If local elements are written to synced fields it can cause exceptions and crashes + if (allComponent.IsLocalElement) continue; + var result = componentDataFeedData.AddComponent(allComponent); + foreach (ISyncMember syncMember in allComponent.SyncMembers) + { + if (FilterMember(syncMember)) + { + result.data.AddMember(syncMember); + } + } } } diff --git a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeedData.cs b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeedData.cs index 4d99757..bdb4924 100644 --- a/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeedData.cs +++ b/ProjectObsidian/Components/Radiant UI/Data Feeds/Feeds/ComponentsDataFeedData.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System; using FrooxEngine; +using Microsoft.CodeAnalysis.Operations; namespace Obsidian; @@ -18,62 +19,105 @@ public void Clear() _dataByUniqueId.Clear(); } - public ComponentData RegisterComponent(Component c) + private string GetUniqueId(Component c) + { + return c.ReferenceID.ToString(); + } + + private string GetUniqueId(Type type) + { + return type.GetHashCode().ToString(); + } + + private ComponentData RegisterComponent(Component c, out bool createdEntry) { - bool created; - ComponentData componentData = EnsureEntry(c, out created); + ComponentData componentData = EnsureEntry(c, out createdEntry); - if (!created) + if (!createdEntry) { - throw new InvalidOperationException("Component with this ReferenceID has already been added!"); + throw new InvalidOperationException("Component with this ReferenceID has already been added! RefId: " + GetUniqueId(c)); } - componentData.component = c; - return componentData; - } - - private ComponentData RegisterMember(ISyncMember member, out bool createdEntry) - { - ComponentData componentData = EnsureEntry(member.FindNearestParent(), out createdEntry); - componentData.AddMember(member); - _dataByUniqueId[member.ReferenceID.ToString()] = componentData; return componentData; } - public ComponentDataResult AddMember(ISyncMember member) + public ComponentDataResult AddComponent(Component c) { bool createdEntry; - return new ComponentDataResult(RegisterMember(member, out createdEntry), (!createdEntry) ? DataFeedItemChange.Updated : DataFeedItemChange.Added); + return new ComponentDataResult(RegisterComponent(c, out createdEntry), (!createdEntry) ? DataFeedItemChange.Updated : DataFeedItemChange.Added); } public ComponentDataResult RemoveComponent(Component c) { - if (!_dataByUniqueId.TryGetValue(c.ReferenceID.ToString(), out var value)) + if (!_dataByUniqueId.TryGetValue(GetUniqueId(c), out var value)) { return new ComponentDataResult(null, DataFeedItemChange.Unchanged); } - _dataByUniqueId.Remove(c.ReferenceID.ToString()); RemoveEntry(value); return new ComponentDataResult(value, DataFeedItemChange.Removed); } - private void RemoveEntry(ComponentData data) + private ComponentData RegisterComponentType(Type type, out bool createdEntry) + { + ComponentData componentData = EnsureEntry(type, out createdEntry); + + if (!createdEntry) + { + throw new InvalidOperationException("Component with this Type has already been added! Type: " + GetUniqueId(type)); + } + + return componentData; + } + + public ComponentDataResult AddComponentType(Type type) + { + bool createdEntry; + return new ComponentDataResult(RegisterComponentType(type, out createdEntry), (!createdEntry) ? DataFeedItemChange.Updated : DataFeedItemChange.Added); + } + + public ComponentDataResult RemoveComponentType(Type type) + { + if (!_dataByUniqueId.TryGetValue(GetUniqueId(type), out var value)) + { + return new ComponentDataResult(null, DataFeedItemChange.Unchanged); + } + RemoveEntry(value); + return new ComponentDataResult(value, DataFeedItemChange.Removed); + } + + private void RemoveEntry(ComponentData data) { _data.Remove(data); - _dataByUniqueId.Remove(data.component.ReferenceID.ToString()); + _dataByUniqueId.Remove(data.uniqueId); } private ComponentData EnsureEntry(Component c, out bool created) { - if (_dataByUniqueId.TryGetValue(c.ReferenceID.ToString(), out var value)) + if (_dataByUniqueId.TryGetValue(GetUniqueId(c), out var value)) { created = false; return value; } value = new ComponentData(c); + value.uniqueId = GetUniqueId(c); _data.Add(value); - _dataByUniqueId.Add(c.ReferenceID.ToString(), value); + _dataByUniqueId.Add(GetUniqueId(c), value); created = true; return value; } + + private ComponentData EnsureEntry(Type type, out bool created) + { + if (_dataByUniqueId.TryGetValue(GetUniqueId(type), out var value)) + { + created = false; + return value; + } + value = new ComponentData(type); + value.uniqueId = GetUniqueId(type); + _data.Add(value); + _dataByUniqueId.Add(GetUniqueId(type), value); + created = true; + return value; + } } \ No newline at end of file diff --git a/ProjectObsidian/Components/Radiant UI/Data Feeds/Interfaces/ComponentDataItemInterface.cs b/ProjectObsidian/Components/Radiant UI/Data Feeds/Interfaces/ComponentDataItemInterface.cs index d9d7692..23eddf7 100644 --- a/ProjectObsidian/Components/Radiant UI/Data Feeds/Interfaces/ComponentDataItemInterface.cs +++ b/ProjectObsidian/Components/Radiant UI/Data Feeds/Interfaces/ComponentDataItemInterface.cs @@ -1,4 +1,5 @@ -using FrooxEngine; +using System; +using FrooxEngine; namespace Obsidian; @@ -7,6 +8,12 @@ public class ComponentDataItemInterface : FeedItemInterface { public readonly SyncRef> Component; + public readonly SyncRef> Type; + + public readonly SyncRef> IsGenericType; + + public readonly SyncRef> GenericTypeDefinition; + public readonly SyncRef> MemberCount; public readonly FeedSubTemplate, FeedEntityInterface> Members; @@ -17,6 +24,9 @@ public override void Set(IDataFeedView view, DataFeedItem item) if (item is ComponentDataFeedItem componentDataFeedItem) { Component.TrySetTarget(componentDataFeedItem.Data.component); + Type.TrySetTarget(componentDataFeedItem.Data.ComponentType); + IsGenericType.TrySetTarget(componentDataFeedItem.Data.IsGenericType); + GenericTypeDefinition.TrySetTarget(componentDataFeedItem.Data.GenericTypeDefinition); MemberCount.TrySetTarget(componentDataFeedItem.Data.MemberCount); Members.Set(componentDataFeedItem.Members, view); } diff --git a/ProjectObsidian/ProjectObsidian.csproj b/ProjectObsidian/ProjectObsidian.csproj index 544d88c..fa13885 100644 --- a/ProjectObsidian/ProjectObsidian.csproj +++ b/ProjectObsidian/ProjectObsidian.csproj @@ -54,7 +54,10 @@ $(ResonitePath)/Resonite_Data/Managed/SteamVR.dll - + + + $(ResonitePath)/Resonite_Data/Managed/System.ValueTuple.dll +