11using System ;
2- using System . Collections ;
32using System . Collections . Generic ;
43using UnityEngine ;
54using UnityEngine . UIElements ;
@@ -16,7 +15,7 @@ public class MessageFeed : MonoBehaviour
1615 [ SerializeField ]
1716 UIDocument doc ;
1817
19- List < Message > m_Messages ;
18+ List < MessageViewModel > m_Messages ;
2019
2120 VisualElement m_MessageContainer ;
2221
@@ -96,37 +95,30 @@ void OnLifeStateChangedEvent(LifeStateChangedEventMessage eventMessage)
9695 break ;
9796 }
9897 }
98+
9999
100- [ ContextMenu ( "Add RandomMessage " ) ]
101- public void AddRandomMessage ( )
100+ [ ContextMenu ( "Add Message " ) ]
101+ public void AddMessage ( )
102102 {
103- var rand = new Random ( nameof ( AddRandomMessage ) . GetHashCode ( ) ) ;
104- m_Messages . Add ( new Message { isShown = true , startTime = Time . realtimeSinceStartup , message = rand . Next ( ) . ToString ( ) } ) ;
103+ ShowMessage ( "Hello!" ) ;
105104 }
106105
107106 void Start ( )
108107 {
109108 var root = doc . rootVisualElement ;
110109
111- // you could load this template from a template uxml, don't need to be a part of the visual tree
112- // could make it a prop on this monobehaviour
113- // VisualTreeAsset messageTempalte; or load it via Resources api, than you don't need to hide them
114- var templateLabel = root . Q < Label > ( "messageLabel" ) ;
115- var templateBox = root . Q < VisualElement > ( "messageBox" ) ;
110+ m_Messages = new List < MessageViewModel > ( ) ;
116111
117- // Hide the default template elements
118- templateLabel . style . display = DisplayStyle . None ;
119- templateBox . style . display = DisplayStyle . None ;
120-
121- m_Messages = new List < Message > ( ) ;
122-
123- // Create a container for all messages
112+ // Find the container of all messages
124113 var listView = root . Q < ListView > ( "messageList" ) ;
114+
115+ // Since you've added an item template in the UXML this is not really necessary here
125116 listView . makeItem += ( ) =>
126117 {
127118 // Create a new message if no reusable messages are available
128119 var newBox = new VisualElement ( ) ;
129120 newBox . AddToClassList ( "messageBox" ) ;
121+
130122 //newBox.style.position = Position.Absolute; // Explicitly position it
131123
132124 // Position the new message box below the last message
@@ -136,29 +128,38 @@ void Start()
136128 newLabel . AddToClassList ( "message" ) ;
137129 newBox . Add ( newLabel ) ;
138130
131+ // the even when the control get's added to the "UI Canvas"
132+ newBox . RegisterCallback < AttachToPanelEvent > ( ( e ) =>
133+ ( e . target as VisualElement ) ? . AddToClassList ( "messageBoxMove" ) ) ;
139134
140- newBox . style . opacity = 1 ;
141- newBox . style . display = DisplayStyle . Flex ;
135+ // fires before the element is actually removed
136+ newBox . RegisterCallback < DetachFromPanelEvent > ( ( e ) =>
137+ {
138+ if ( e . target is VisualElement )
139+ {
140+
141+ }
142+ } ) ;
142143
143- newBox . RegisterCallback < AttachToPanelEvent > ( ( e ) => StartCoroutine ( FlyInWithBounce ( e . target as VisualElement , - 300 , 50 , 0.2f , 0.2f ) ) ) ;
144-
145144 return newBox ;
146145 } ;
147146
148147 listView . bindItem += ( element , i ) =>
149148 {
150- element . Q < Label > ( ) . text = m_Messages [ i ] . message ;
149+ element . Q < Label > ( ) . text = m_Messages [ i ] . Message ;
151150 } ;
152151
153152 // collection change events will take care of creating and disposing items
154153 listView . itemsSource = m_Messages ;
155-
156154
157- m_MessageContainer = listView ;
158- m_MessageContainer . style . flexDirection = FlexDirection . Column ; // Arrange messages vertically
155+ // [IMPORTANT!] try to set as much of the style related logic as you can in the USS files instead.
156+ /*
157+ m_MessageContainer = listView;
158+ m_MessageContainer.style.flexDirection = FlexDirection.Column; // Arrange messages vertically
159159
160160 // make sure other visual elements don't get pushed down by the message container
161161 m_MessageContainer.style.position = Position.Absolute;
162+ */
162163 }
163164
164165 void OnDestroy ( )
@@ -171,28 +172,36 @@ void OnDestroy()
171172
172173 void Update ( )
173174 {
175+ var messagesToRemove = new List < MessageViewModel > ( ) ;
174176 foreach ( var m in m_Messages )
175177 {
176- if ( m . isShown )
178+ if ( m . ShouldDispose ( ) )
177179 {
178- // Check if a message should begin fading out
179- // if (Time.realtimeSinceStartup - m.startTime > 5 && m.style.opacity == 1)
180- //{
181- //StartFadeout(m, 1f);
182- // m.isShown = false;
183- //}
180+ messagesToRemove . Add ( m ) ;
184181 }
182+
183+ // Check if a message should begin fading out
184+ // if (Time.realtimeSinceStartup - m.startTime > 5 && m.style.opacity == 1)
185+ //{
186+ //StartFadeout(m, 1f);
187+ // m.isShown = false;
188+ //}
189+ }
190+
191+ // TODO: start animation via events
192+ foreach ( var m in messagesToRemove )
193+ {
194+ m_Messages . Remove ( m ) ;
185195 }
186196 }
187197
188198 void ShowMessage ( string message )
189199 {
190- const float messageHeight = 40f ; // Approximate height of a message (adjust based on UI)
191- const float verticalSpacing = 10f ; // Spacing between stacked messages
192-
193200 // Reuse or create a new message
194- Message newMessage = null ;
201+ MessageViewModel newMessage = null ;
195202
203+ // a list view's virtualization logic actually takes care of this by default
204+ /*
196205 foreach (var m in m_Messages)
197206 {
198207 if (!m.isShown)
@@ -202,71 +211,10 @@ void ShowMessage(string message)
202211 break;
203212 }
204213 }
214+ */
215+ newMessage = new MessageViewModel ( message , TimeSpan . FromSeconds ( 5 ) ) ;
205216
206- if ( newMessage == null )
207- {
208- newMessage = new Message ( )
209- {
210- isShown = true ,
211- startTime = Time . realtimeSinceStartup ,
212- } ;
213-
214- m_Messages . Add ( newMessage ) ; // Add to the list of messages
215- }
216-
217- // Set the properties of the reused or new message
218- newMessage . isShown = true ;
219-
220- newMessage . startTime = Time . realtimeSinceStartup ;
221- newMessage . message = message ;
222- }
223-
224- IEnumerator FlyInWithBounce ( VisualElement element , float startLeft , float targetLeft , float duration , float bounceDuration )
225- {
226- float elapsedTime = 0 ;
227- element . style . opacity = 0 ;
228-
229- // Main fly-in animation (linear movement from off-screen)
230- while ( elapsedTime < duration )
231- {
232- elapsedTime += Time . deltaTime ;
233- float t = elapsedTime / duration ; // Normalized time (0 to 1)
234-
235- // Linearly interpolate left position
236- float newLeft = Mathf . Lerp ( startLeft , targetLeft , t ) ;
237- element . style . left = newLeft ;
238-
239- // Gradually fade in
240- element . style . opacity = t ;
241-
242- yield return null ;
243- }
244-
245- // Ensure the message is at the target final position
246- element . style . left = targetLeft ;
247- element . style . opacity = 1 ;
248-
249- // Bounce Animation: Overshoot to the right and come back
250- float overshootAmount = 20 ;
251- float bounceElapsedTime = 0 ;
252-
253- while ( bounceElapsedTime < bounceDuration )
254- {
255- bounceElapsedTime += Time . deltaTime ;
256- float t = bounceElapsedTime / bounceDuration ;
257-
258- // Use a simple sine easing for the bounce effect
259- float bounceT = Mathf . Sin ( t * Mathf . PI ) ;
260-
261- // Interpolate between targetLeft and overshoot position
262- float bounceLeft = Mathf . Lerp ( targetLeft , targetLeft + overshootAmount , bounceT ) ;
263-
264- element . style . left = bounceLeft ;
265- yield return null ;
266- }
267-
268- // Finally snap back to the exact target position
269- element . style . left = targetLeft ;
217+ m_Messages . Add ( newMessage ) ; // Add to the list of messages
270218 }
271219
272220 /*
@@ -289,10 +237,23 @@ static void StartFadeout(Message message, float opacity)
289237 */
290238
291239 // if you bind the itemsource to the list you don't actually have to manually do this
292- class Message
240+ private class MessageViewModel
293241 {
294- public bool isShown ;
295- public float startTime ; // The time when the message was shown
296- public string message ;
242+ private readonly TimeSpan _autoDispose ;
243+ private DateTime _createdAt ;
244+
245+ public string Message { get ; }
246+
247+ public MessageViewModel ( string message , TimeSpan timeout = default )
248+ {
249+ _createdAt = DateTime . Now ;
250+ _autoDispose = timeout ;
251+ Message = message ;
252+ }
253+
254+ public bool ShouldDispose ( )
255+ {
256+ return _createdAt + _autoDispose < DateTime . Now ;
257+ }
297258 }
298259}
0 commit comments