11using System ;
2- using System . Collections ;
32using System . Collections . Generic ;
3+ using System . Collections . ObjectModel ;
4+ using System . Collections . Specialized ;
45using UnityEngine ;
56using UnityEngine . UIElements ;
67using Unity . BossRoom . ConnectionManagement ;
@@ -20,10 +21,10 @@ public class MessageFeed : MonoBehaviour
2021 [ SerializeField ]
2122 UIDocument doc ;
2223
23- List < MessageViewModel > m_Messages ;
24+ ObservableCollection < MessageViewModel > m_Messages ;
2425 List < MessageViewModel > m_MessagesToRemove = new List < MessageViewModel > ( ) ;
2526
26- ListView m_MessageContainer ;
27+ VisualElement m_MessageContainer ;
2728
2829 DisposableGroup m_Subscriptions ;
2930
@@ -108,56 +109,18 @@ public void AddMessage()
108109 ShowMessage ( $ "Hello! { DateTime . Now . Millisecond } ") ;
109110 }
110111
111- //would be much nicer if this would be a custom control, and we'd do this in an attach to panel event
112+ // would be much nicer if this would be a custom control, and we'd do this in an attach to panel event
112113 void Start ( )
113114 {
114115 var root = doc . rootVisualElement ;
115116
116- m_Messages = new List < MessageViewModel > ( ) ;
117+ m_Messages = new ObservableCollection < MessageViewModel > ( ) ;
117118
118119 // Find the container of all messages
119- var listView = root . Q < ListView > ( "messageList " ) ;
120+ m_MessageContainer = root . Q < VisualElement > ( "messageFeed " ) ;
120121
121- // Since you've added an item template in the UXML this is not really necessary here
122- listView . makeItem += ( ) =>
123- {
124- // Create a new message if no reusable messages are available
125- var newBox = new VisualElement ( ) ;
126- newBox . AddToClassList ( k_MessageBoxClassName ) ;
127-
128- var newLabel = new Label ( ) ;
129- newLabel . AddToClassList ( k_MessageClassName ) ;
130- newBox . Add ( newLabel ) ;
131-
132- // the event when the control get's added to the "UI Canvas"
133- newBox . RegisterCallback < AttachToPanelEvent > ( ( e ) =>
134- {
135- if ( e . target is VisualElement element )
136- {
137- element . RemoveFromClassList ( k_MessageBoxMovementClassName ) ;
138- StartCoroutine ( ToggleClassWithDelay ( element , k_MessageBoxMovementClassName , TimeSpan . FromSeconds ( 0.02 ) ) ) ;
139- }
140- } ) ;
141-
142- // fires just before the element is actually removed
143- newBox . RegisterCallback < DetachFromPanelEvent > ( ( e ) =>
144- {
145- if ( e . target is VisualElement ) { }
146- } ) ;
147-
148- return newBox ;
149- } ;
150-
151- // use this to set bindings / values on your view components
152- listView . bindItem += ( element , i ) =>
153- {
154- var label = element . Q < Label > ( ) ;
155- label . text = m_Messages [ i ] . Message ;
156- } ;
157-
158- // collection change events will take care of creating and disposing items
159- listView . itemsSource = m_Messages ;
160- m_MessageContainer = listView ;
122+ // This will handle the addition and removal of the message presenters
123+ m_Messages . CollectionChanged += OnMessageCollectionChanged ;
161124 }
162125
163126 void OnDestroy ( )
@@ -184,39 +147,19 @@ void Update()
184147 foreach ( var message in m_MessagesToRemove )
185148 {
186149 var childQuery = m_MessageContainer . Query < VisualElement > ( ) . Class ( k_MessageBoxClassName ) ;
187- var child = childQuery . AtIndex ( m_Messages . IndexOf ( message ) ) ;
150+ var child = childQuery . Where ( a => a . Q < Label > ( ) . text == message . Message ) . First ( ) ;
188151
189152 if ( ! child . ClassListContains ( k_FadeOutClassName ) )
190153 {
191154 child . AddToClassList ( k_FadeOutClassName ) ;
192155 child . RegisterCallback < TransitionEndEvent > ( OnTransitionEndEvent ) ;
193- child . RegisterCallback < TransitionCancelEvent > ( OnTransitionCancelEvent ) ;
194156 }
195157
196- void OnTransitionCancelEvent ( TransitionCancelEvent e )
197- {
198- m_Messages . Remove ( message ) ;
199- m_MessagesToRemove . Remove ( message ) ;
200- if ( e . target is VisualElement element )
201- {
202- element . RemoveFromClassList ( k_FadeOutClassName ) ;
203- }
204- }
205-
206158 // local event handler function
207159 void OnTransitionEndEvent ( TransitionEndEvent e )
208160 {
209- if ( e . target is VisualElement element )
161+ if ( e . target is VisualElement element )
210162 {
211- // remove subscription
212- element . UnregisterCallback < TransitionEndEvent > ( OnTransitionEndEvent ) ;
213-
214- element . RemoveFromClassList ( k_FadeOutClassName ) ;
215-
216- // moving the message to be the last visualized in the container
217- // effectively stopping the list view virtualization caused animation issues
218- m_MessageContainer . viewController . Move ( m_Messages . IndexOf ( message ) , m_Messages . Count - 1 ) ;
219-
220163 m_Messages . Remove ( message ) ;
221164 m_MessagesToRemove . Remove ( message ) ;
222165 }
@@ -226,19 +169,81 @@ void OnTransitionEndEvent(TransitionEndEvent e)
226169
227170 void ShowMessage ( string message )
228171 {
172+ // we create a new message view model that will time out in 5 seconds
229173 var newMessage = new MessageViewModel ( message , TimeSpan . FromSeconds ( 5 ) ) ;
230-
174+ // we add the message to the observable collection, triggering the visualization
231175 m_Messages . Add ( newMessage ) ; // Add to the list of messages
232176 }
233177
234- IEnumerator ToggleClassWithDelay ( VisualElement element , string className , TimeSpan delay )
178+ private void OnMessageCollectionChanged ( object sender , NotifyCollectionChangedEventArgs eventArgs )
179+ {
180+ switch ( eventArgs . Action )
181+ {
182+ case NotifyCollectionChangedAction . Add :
183+ OnMessagesAdded ( eventArgs ) ;
184+ break ;
185+ case NotifyCollectionChangedAction . Remove :
186+ OnMessagesRemoved ( eventArgs ) ;
187+ break ;
188+ default :
189+ Debug . LogWarning ( "Collection was modified in an unexpected way" ) ;
190+ break ;
191+ }
192+ }
193+
194+ private void OnMessagesRemoved ( NotifyCollectionChangedEventArgs eventArgs )
235195 {
236- yield return new WaitForSeconds ( ( float ) delay . TotalSeconds ) ;
196+ foreach ( var itemToRemove in eventArgs . OldItems )
197+ {
198+ if ( itemToRemove is MessageViewModel messageViewModel )
199+ {
200+ var childQuery = m_MessageContainer . Query < VisualElement > ( ) . Class ( k_MessageBoxClassName ) ;
201+ var child = childQuery . Where ( a => a . Q < Label > ( ) . text == messageViewModel . Message ) . First ( ) ;
202+ // manually removing the child item from the message feed
203+ m_MessageContainer . contentContainer . Remove ( child ) ;
204+ }
205+ }
206+ }
207+
208+ private void OnMessagesAdded ( NotifyCollectionChangedEventArgs eventArgs )
209+ {
210+ foreach ( var message in eventArgs . NewItems )
211+ {
212+ if ( message is not MessageViewModel messageViewModel )
213+ return ;
237214
238- element . ToggleInClassList ( className ) ;
215+ // Create a new messageBox
216+ var messageBox = new VisualElement ( ) ;
217+ messageBox . AddToClassList ( k_MessageBoxClassName ) ;
218+
219+ var messagePresenter = new Label ( ) ;
220+ messagePresenter . AddToClassList ( k_MessageClassName ) ;
221+ messagePresenter . text = messageViewModel . Message ;
222+ // Add the message presenter into the box
223+ messageBox . Add ( messagePresenter ) ;
224+
225+ // the event when the control get's added to the "UI Canvas"
226+ messageBox . RegisterCallback < AttachToPanelEvent > ( OnAttachToPanelEvent ) ;
227+
228+ // Add the message box to the message Feed
229+ m_MessageContainer . contentContainer . Add ( messageBox ) ;
230+
231+ return ;
232+
233+ void OnAttachToPanelEvent ( AttachToPanelEvent evt )
234+ {
235+ if ( evt . target is VisualElement element )
236+ {
237+ // we set up the control in a way that it starts with an offset.
238+ // we schedule the transition for the message to snap in back to it's intended position
239+ element . schedule
240+ . Execute ( ( ) => element . ToggleInClassList ( k_MessageBoxMovementClassName ) )
241+ . ExecuteLater ( 200 ) ;
242+ }
243+ }
244+ }
239245 }
240246
241- // if you bind the itemsource to the list you don't actually have to manually do this
242247 class MessageViewModel
243248 {
244249 readonly TimeSpan _autoDispose ;
0 commit comments