fixed that fucking ui problem!!! thank you kas <3

This commit is contained in:
Rowan 2025-08-19 05:51:22 -04:00
parent 9f1ebe05a1
commit d39e6895af
10 changed files with 2786 additions and 163 deletions

View file

@ -0,0 +1,137 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-6002683583417286212
MonoBehaviour:
m_ObjectHideFlags: 11
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
m_Name:
m_EditorClassIdentifier:
version: 10
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: New Material
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap:
RenderType: Opaque
disabledShaderPasses:
- MOTIONVECTORS
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BaseMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _SpecGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_Lightmaps:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_LightmapsInd:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_ShadowMasks:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AddPrecomputedVelocity: 0
- _AlphaClip: 0
- _AlphaToMask: 0
- _Blend: 0
- _BlendModePreserveSpecular: 1
- _BumpScale: 1
- _ClearCoatMask: 0
- _ClearCoatSmoothness: 0
- _Cull: 2
- _Cutoff: 0.5
- _DetailAlbedoMapScale: 1
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _DstBlendAlpha: 0
- _EnvironmentReflections: 1
- _GlossMapScale: 0
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 0
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0.5
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 1, g: 0, b: 0, a: 1}
- _Color: {r: 1, g: 0, b: 0, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 63b9ee8d7c472fcdd9d64669d3d96daf
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -14,6 +14,6 @@ MonoBehaviour:
m_EditorClassIdentifier:
Description:
value:
value: 0
value: -50
clamp: 1
minMax: {x: -50, y: 10}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using KitsuneCafe.Sys;
using ObservableCollections;
using UnityEngine;
@ -92,11 +94,14 @@ namespace KitsuneCafe.ItemSystem
[SerializeField]
private int capacity = 8;
public event NotifyCollectionChangedEventHandler<InventoryItem> CollectionChanged;
public int Capacity => capacity;
public int Count => items.Count;
public bool IsEmpty => Count == 0;
public bool IsFull => Count == Capacity;
public object SyncRoot => ((IList)items).SyncRoot;
@ -107,7 +112,11 @@ namespace KitsuneCafe.ItemSystem
public bool IsSynchronized => ((ICollection)items).IsSynchronized;
object IList.this[int index] { get => items[index]; set => items[index] = (InventoryItem)value; }
object IList.this[int index]
{
get => items[index];
set => items[index] = (InventoryItem)value;
}
public InventoryItem this[int index]
{
@ -115,6 +124,7 @@ namespace KitsuneCafe.ItemSystem
set => items[index] = value;
}
public IEnumerable<InventoryItem> Find(Func<InventoryItem, bool> predicate)
{
for (int i = 0; i < Count; i++)
@ -135,9 +145,12 @@ namespace KitsuneCafe.ItemSystem
return Result.Err<int, InventoryError>(InventoryError.InvalidQuantity);
}
List<InventoryItem> existing = new();
List<InventoryItem> updated = new();
for (int i = 0; i < Count; i++)
{
var existingItem = items[i];
existing.Add(existingItem);
if (existingItem.Equals(item))
{
@ -147,6 +160,7 @@ namespace KitsuneCafe.ItemSystem
}
var consumedAmount = existingItem.IncreaseCount(count, out var updatedItem);
updated.Add(updatedItem.Value);
items[i] = updatedItem.Value;
count -= consumedAmount;
@ -157,11 +171,13 @@ namespace KitsuneCafe.ItemSystem
}
}
Notify(NotifyCollectionChangedAction.Replace, updated, existing);
return Result.Ok<int, InventoryError>(count);
}
private IResult<Unit, InventoryError> AddNew(Item item, int count)
{
List<InventoryItem> added = new();
while (count > 0)
{
if (IsFull)
@ -171,9 +187,10 @@ namespace KitsuneCafe.ItemSystem
count -= InventoryItem.Create(item, count, out var newItem);
items.Add(newItem.Value);
added.Add(newItem.Value);
}
Notify(NotifyCollectionChangedAction.Add, added);
return Result.Ok<Unit, InventoryError>(default);
}
@ -189,6 +206,32 @@ namespace KitsuneCafe.ItemSystem
};
}
private void Notify(NotifyCollectionChangedAction action, IList<InventoryItem> newItems = default, IList<InventoryItem> oldItems = default)
{
NotifyCollectionChangedEventArgs<InventoryItem> args;
if (newItems.Count == 1)
{
args = new(
action,
true,
newItems[0],
oldItems[0]
);
}
else
{
args = new(
action,
false,
newItems: newItems.ToArray(),
oldItems: oldItems.ToArray()
);
}
CollectionChanged?.Invoke(args);
}
public IResult<Unit, InventoryError> Remove(Item item, int count = 1)
{
if (count <= 0)
@ -196,6 +239,8 @@ namespace KitsuneCafe.ItemSystem
return Result.Err<Unit, InventoryError>(InventoryError.InvalidQuantity);
}
List<InventoryItem> removed = new();
List<InventoryItem> updated = new();
for (int i = items.Count - 1; i >= 0; i--)
{
var existingItem = items[i];
@ -206,15 +251,22 @@ namespace KitsuneCafe.ItemSystem
{
existingItem.ReduceCount(count, out var updatedItem);
items[i] = updatedItem.Value;
removed.Add(existingItem);
updated.Add(updatedItem.Value);
Notify(NotifyCollectionChangedAction.Remove, updated, removed);
return Result.Ok<Unit, InventoryError>(Unit.Default);
}
else
{
count -= existingItem.Count;
items.RemoveAt(i);
removed.Add(existingItem);
if (count == 0)
{
Notify(NotifyCollectionChangedAction.Remove, updated, removed);
return Result.Ok<Unit, InventoryError>(Unit.Default);
}
}
@ -226,6 +278,7 @@ namespace KitsuneCafe.ItemSystem
return Result.Err<Unit, InventoryError>(InventoryError.NotEnoughQuantity);
}
Notify(NotifyCollectionChangedAction.Remove, updated, removed);
return Result.Ok<Unit, InventoryError>(Unit.Default);
}
@ -291,5 +344,71 @@ namespace KitsuneCafe.ItemSystem
{
((ICollection)items).CopyTo(array, index);
}
// public ISynchronizedView<InventoryItem, TView> CreateView<TView>(Func<InventoryItem, TView> transform)
// {
// }
}
public class InventoryView<TView> : ISynchronizedView<InventoryItem, TView>
{
private Inventory inventory;
public object SyncRoot => inventory.SyncRoot;
private ISynchronizedViewFilter<InventoryItem, TView> filter;
public ISynchronizedViewFilter<InventoryItem, TView> Filter => filter;
public IEnumerable<(InventoryItem Value, TView View)> Filtered => throw new NotImplementedException();
public IEnumerable<(InventoryItem Value, TView View)> Unfiltered => throw new NotImplementedException();
public int UnfilteredCount => throw new NotImplementedException();
public int Count => throw new NotImplementedException();
public event NotifyViewChangedEventHandler<InventoryItem, TView> ViewChanged;
public event Action<RejectedViewChangedAction, int, int> RejectedViewChanged;
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
public void AttachFilter(ISynchronizedViewFilter<InventoryItem, TView> filter)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public IEnumerator<TView> GetEnumerator()
{
throw new NotImplementedException();
}
public void ResetFilter()
{
throw new NotImplementedException();
}
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{
throw new NotImplementedException();
}
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher collectionEventDispatcher)
{
throw new NotImplementedException();
}
public ISynchronizedViewList<TView> ToViewList()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -11,7 +11,7 @@ namespace KitsuneCafe.UI
{
public class RecyclerViewModel
{
public record RotateEvent(Direction Direction, int FirstIndex);
public record RotateEvent(Direction Direction, int FirstIndex, object CurrentItem);
public enum Direction
{
@ -29,7 +29,6 @@ namespace KitsuneCafe.UI
if (displayCount != value)
{
displayCount = value;
CreateIndices();
}
}
}
@ -43,15 +42,14 @@ namespace KitsuneCafe.UI
if (itemSource != value)
{
itemSource = value;
CreateIndices();
}
}
}
public int Count => Math.Min(DisplayCount, ItemSource.Count);
public int Count => Math.Max(displayCount, itemSource.Count);
private List<int> indices;
public IReadOnlyList<int> Indices => indices;
private int currentIndex = 0;
public int SelectedIndex => currentIndex;
public readonly ICommand<Direction> RotateCommand;
@ -59,52 +57,113 @@ namespace KitsuneCafe.UI
public RecyclerViewModel()
{
indices = new();
RotateCommand = new RelayCommand<Direction>(CanRotate, Rotate);
}
private void CreateIndices()
{
if (itemSource == null || displayCount == 0) { return; }
indices = Enumerable.Range(0, Math.Min(DisplayCount, ItemSource.Count)).ToList();
}
private bool CanRotate(Direction direction)
{
return direction == Direction.Clockwise || direction == Direction.CounterClockwise;
}
public static int WrapIndex(int index, int count)
{
return (index + count) % count;
}
public int WrapIndex(int index)
{
return WrapIndex(index, Count);
}
public int GetOffset(int index)
{
return WrapIndex(currentIndex + index);
}
public static T GetItem<T>(IList<T> xs, int index)
{
return xs[WrapIndex(index, xs.Count)];
}
public static object GetItem(IList xs, int index)
{
return xs[WrapIndex(index, xs.Count)];
}
public object GetItem(int index)
{
if (0 > index || index >= itemSource.Count) { return null; }
return GetItem(itemSource, index);
}
public static IEnumerable<T> GetSlice<T>(IList<T> xs, int first, int count)
{
var diff = count - xs.Count;
var xs2 = new List<T>(xs);
xs2.AddRange(Enumerable.Repeat<T>(default, diff));
var len = first + count;
for (int i = first; i < len; i++)
{
yield return GetItem<T>(xs2, i);
}
}
public static IEnumerable GetSlice(IList xs, int first, int count)
{
var diff = count - xs.Count;
var xs2 = xs.Cast<object>().ToList();
xs2.AddRange(Enumerable.Repeat<object>(default, diff));
var len = first + count;
for (int i = first; i < len; i++)
{
yield return GetItem<object>(xs2, i);
}
}
public IEnumerable GetSlice(int first, int count)
{
return GetSlice(itemSource, first, count);
}
public void Rotate(Direction direction)
{
if (direction == Direction.Clockwise)
var dir = direction switch
{
indices.RemoveAt(0);
var next = indices.Last() + 1;
next %= ItemSource.Count;
indices.Add(next);
Notify(direction);
}
else if (direction == Direction.CounterClockwise)
Direction.Clockwise => 1,
Direction.CounterClockwise => -1,
_ => 0
};
if (dir != 0)
{
indices.RemoveAt(ItemSource.Count - 1);
var count = ItemSource.Count;
var previous = indices[0] - 1;
previous += count % count;
indices.Insert(0, previous);
currentIndex = WrapIndex(currentIndex + dir);
Notify(direction);
}
}
private void Notify(Direction direction)
{
Rotated?.Invoke(this, new RotateEvent(direction, indices.First()));
Rotated?.Invoke(this, new RotateEvent(
direction,
currentIndex,
GetItem(currentIndex)
));
}
}
public class SelectEvent : EventBase<SelectEvent>
{
public int ElementIndex { get; set; }
public int DataIndex { get; set; }
public int SelectedIndex;
public object SelectedItem;
protected override void Init()
{
base.Init();
bubbles = true;
tricklesDown = true;
}
}
[UxmlElement]
@ -138,17 +197,17 @@ namespace KitsuneCafe.UI
}
}
private IBinder binder;
private VisualTreeAsset template;
[CreateProperty]
public IBinder Binder
[UxmlAttribute, CreateProperty]
public VisualTreeAsset Template
{
get => binder;
get => template;
set
{
if (binder != value)
if (template != value)
{
binder = value;
template = value;
CreateItems();
Rebind();
}
@ -173,18 +232,20 @@ namespace KitsuneCafe.UI
private void CreateItems()
{
Clear();
if (DisplayCount == 0 || binder == null) { return; }
if (DisplayCount == 0 || template == null) { return; }
for (int i = 0; i < DisplayCount; i++)
{
Add(binder.CreateItem());
var ve = template.CloneTree();
ve.focusable = true;
Add(ve);
}
FocusFirst();
}
private void Rebind()
public void Rebind()
{
if (viewModel == null || ItemSource == null || DisplayCount == 0 || binder == null) { return; }
if (viewModel == null || ItemSource == null || DisplayCount == 0) { return; }
for (int i = 0; i < DisplayCount; i++)
{
@ -199,34 +260,35 @@ namespace KitsuneCafe.UI
private void OnRotated(object sender, RecyclerViewModel.RotateEvent e)
{
VisualElement recycled;
switch (e.Direction)
var first = 0;
var last = DisplayCount - 1;
var (from, to) = e.Direction switch
{
case RecyclerViewModel.Direction.Clockwise:
recycled = this[0];
RemoveAt(0);
Insert(DisplayCount - 1, recycled);
TryBindItem(recycled, DisplayCount - 1);
NotifySelection();
break;
case RecyclerViewModel.Direction.CounterClockwise:
recycled = this[DisplayCount - 1];
RemoveAt(DisplayCount - 1);
Insert(0, recycled);
TryBindItem(recycled, 0);
NotifySelection();
break;
}
RecyclerViewModel.Direction.Clockwise => (first, last),
RecyclerViewModel.Direction.CounterClockwise => (last, first),
RecyclerViewModel.Direction.None => (-1, -1)
};
if (from == -1 && to == -1) { return; }
var element = this[from];
RemoveAt(from);
Insert(to, element);
element.dataSource = viewModel.GetItem(viewModel.GetOffset(to));
FocusFirst();
NotifySelection();
}
private void NotifySelection()
{
using SelectEvent evt = SelectEvent.GetPooled();
evt.target = this[0];
evt.ElementIndex = 0;
evt.DataIndex = viewModel.Indices[0];
evt.SelectedIndex = viewModel.SelectedIndex;
evt.SelectedItem = viewModel.GetItem(evt.SelectedIndex);
SendEvent(evt);
}
@ -242,15 +304,15 @@ namespace KitsuneCafe.UI
{
if (HasItem(index))
{
var idx = viewModel.Indices[index];
var idx = viewModel.WrapIndex(index);
if (0 <= idx && idx < ItemSource.Count)
{
binder.BindItem(element, idx);
element.dataSource = ItemSource[idx];
}
}
else
{
binder.UnbindItem(element);
element.dataSource = null;
}
}

View file

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using KitsuneCafe.ItemSystem;
using KitsuneCafe.SOAP;
using KitsuneCafe.UI.MVVM;
using ObservableCollections;
using R3;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
@ -11,7 +12,7 @@ namespace KitsuneCafe.UI
{
public class InventoryScreen : MonoBehaviour
{
public const string ItemListIndicatorsName = "item-list-indicator";
public const string ItemListIndicatorsName = "item-list-indicator-container";
public const string ItemListName = "item-list";
public const string ItemPreviewName = "item-preview";
@ -49,7 +50,8 @@ namespace KitsuneCafe.UI
private VisualElement root => doc.rootVisualElement;
private VisualElement indicators;
private List<VisualElement> indicators;
private RecyclerView itemList;
private VisualElement itemPreview;
private void OnValidate()
@ -92,29 +94,30 @@ namespace KitsuneCafe.UI
private void CreateItemList()
{
var itemList = root.Q<RecyclerView>();
var indicatorContainer = root.Q<VisualElement>(ItemListIndicatorsName);
indicators = indicatorContainer.Children().ToList();
itemList = root.Q<RecyclerView>();
itemList.ItemSource = inventory;
itemList.Binder = new AdHocBinder(
() =>
{
var instance = template.CloneTree();
instance.focusable = true;
return instance;
},
(el, i) =>
{
el.dataSource = inventory[i];
},
el =>
{
el.dataSource = null;
}
);
itemList.RegisterCallback<SelectEvent>(evt =>
{
Debug.Log($"Selected {evt.DataIndex}");
if (evt.SelectedItem == null)
{
selectedItem.Value = null;
}
else
{
selectedItem.Value = ((InventoryItem)evt.SelectedItem).Item;
}
});
inventory.CollectionChanged += RebindItems;
}
private void RebindItems(in NotifyCollectionChangedEventArgs<InventoryItem> e)
{
itemList.Rebind();
}
}
}

View file

@ -10,7 +10,7 @@
<ui:VisualElement name="indicator-five" class="indicator" />
<ui:VisualElement name="indicator-six" class="indicator" />
</ui:VisualElement>
<KitsuneCafe.UI.RecyclerView display-count="5" style="flex-grow: 1; flex-direction: row-reverse;" />
<KitsuneCafe.UI.RecyclerView display-count="5" template="project://database/Assets/UI/ItemSlot.uxml?fileID=9197481963319205126&amp;guid=01b74a9c8f3685fb2880379892a17e46&amp;type=3#ItemSlot" style="flex-grow: 1; flex-direction: row-reverse;" />
</ui:VisualElement>
<ui:VisualElement name="detail-container" class="spaced-children" style="flex-grow: 1.5; flex-direction: row;">
<ui:VisualElement name="condition-container" class="container" style="flex-grow: 0.65; flex-basis: 0;">
@ -57,16 +57,16 @@
<ui:VisualElement name="preview-header" class="header" />
<KitsuneCafe.UI.PreviewView style="background-image: url(&quot;project://database/Assets/UI/Item%20Preview.renderTexture?fileID=8400000&amp;guid=fc19a022409ab0d408fa0e78cfeab827&amp;type=2#Item Preview&quot;); -unity-background-scale-mode: scale-to-fit; flex-grow: 1;" />
</ui:VisualElement>
<ui:VisualElement name="item-details-container" data-source="project://database/Assets/SOAP/Items/SelectedItem.asset?fileID=11400000&amp;guid=37d00232a0bee1e8e923705187ff2187&amp;type=2#SelectedItem" class="container" style="flex-grow: 1; flex-basis: 0;">
<ui:VisualElement name="item-details-container" class="container" style="flex-grow: 1; flex-basis: 0;">
<ui:VisualElement name="item-details-header" class="header" />
<ui:VisualElement name="item-details-content" style="flex-grow: 0;">
<ui:VisualElement name="item-details-content" data-source="project://database/Assets/SOAP/Items/SelectedItem.asset?fileID=11400000&amp;guid=37d00232a0bee1e8e923705187ff2187&amp;type=2#SelectedItem" style="flex-grow: 0;">
<ui:VisualElement name="item-details-title-container" style="flex-wrap: nowrap; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px;">
<ui:Label text="ITEM NAME" name="item-details-title-label" class="title">
<ui:Label name="item-details-title-label" class="title">
<Bindings>
<ui:DataBinding property="text" data-source-path="value.value.displayName" binding-mode="ToTarget" />
</Bindings>
</ui:Label>
<ui:Label text="Item description body." name="item-details-content-label" style="flex-wrap: nowrap; white-space: pre-wrap; text-overflow: ellipsis;">
<ui:Label name="item-details-content-label" style="flex-wrap: nowrap; white-space: pre-wrap; text-overflow: ellipsis;">
<Bindings>
<ui:DataBinding property="text" data-source-path="value.value.description" binding-mode="ToTarget" />
</Bindings>