Create the visual hierarchy
This commit is contained in:
		
							parent
							
								
									b8740d1974
								
							
						
					
					
						commit
						27c58d3449
					
				|  | @ -0,0 +1,149 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Xamarin.Forms; | ||||||
|  | 
 | ||||||
|  | namespace Ooui.Forms | ||||||
|  | { | ||||||
|  |     public sealed class RendererPool | ||||||
|  |     { | ||||||
|  |         readonly Dictionary<Type, Stack<IVisualElementRenderer>> _freeRenderers = | ||||||
|  |             new Dictionary<Type, Stack<IVisualElementRenderer>> (); | ||||||
|  | 
 | ||||||
|  |         readonly VisualElement _oldElement; | ||||||
|  | 
 | ||||||
|  |         readonly IVisualElementRenderer _parent; | ||||||
|  | 
 | ||||||
|  |         public RendererPool (IVisualElementRenderer renderer, VisualElement oldElement) | ||||||
|  |         { | ||||||
|  |             if (renderer == null) | ||||||
|  |                 throw new ArgumentNullException (nameof (renderer)); | ||||||
|  | 
 | ||||||
|  |             if (oldElement == null) | ||||||
|  |                 throw new ArgumentNullException (nameof (oldElement)); | ||||||
|  | 
 | ||||||
|  |             _oldElement = oldElement; | ||||||
|  |             _parent = renderer; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IVisualElementRenderer GetFreeRenderer (VisualElement view) | ||||||
|  |         { | ||||||
|  |             if (view == null) | ||||||
|  |                 throw new ArgumentNullException (nameof (view)); | ||||||
|  | 
 | ||||||
|  |             var rendererType = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerType (view.GetType()) ?? typeof (Renderers.ViewRenderer); | ||||||
|  | 
 | ||||||
|  |             Stack<IVisualElementRenderer> renderers; | ||||||
|  |             if (!_freeRenderers.TryGetValue (rendererType, out renderers) || renderers.Count == 0) | ||||||
|  |                 return null; | ||||||
|  |              | ||||||
|  |             var renderer = renderers.Pop (); | ||||||
|  |             renderer.SetElement (view); | ||||||
|  |             return renderer; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void UpdateNewElement (VisualElement newElement) | ||||||
|  |         { | ||||||
|  |             if (newElement == null) | ||||||
|  |                 throw new ArgumentNullException ("newElement"); | ||||||
|  | 
 | ||||||
|  |             var sameChildrenTypes = true; | ||||||
|  | 
 | ||||||
|  |             var oldChildren = ((IElementController)_oldElement).LogicalChildren; | ||||||
|  |             var newChildren = ((IElementController)newElement).LogicalChildren; | ||||||
|  | 
 | ||||||
|  |             if (oldChildren.Count == newChildren.Count) { | ||||||
|  |                 for (var i = 0; i < oldChildren.Count; i++) { | ||||||
|  |                     if (oldChildren[i].GetType () != newChildren[i].GetType ()) { | ||||||
|  |                         sameChildrenTypes = false; | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |                 sameChildrenTypes = false; | ||||||
|  | 
 | ||||||
|  |             if (!sameChildrenTypes) { | ||||||
|  |                 ClearRenderers (_parent); | ||||||
|  |                 FillChildrenWithRenderers (newElement); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |                 UpdateRenderers (newElement); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void ClearRenderers (IVisualElementRenderer renderer) | ||||||
|  |         { | ||||||
|  |             if (renderer == null) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             var subviews = renderer.NativeView.Children; | ||||||
|  |             for (var i = 0; i < subviews.Count; i++) { | ||||||
|  |                 var childRenderer = subviews[i] as IVisualElementRenderer; | ||||||
|  |                 if (childRenderer != null) { | ||||||
|  |                     PushRenderer (childRenderer); | ||||||
|  | 
 | ||||||
|  |                     // The ListView CalculateHeightForCell method can create renderers and dispose its child renderers before this is called. | ||||||
|  |                     // Thus, it is possible that this work is already completed. | ||||||
|  |                     if (childRenderer.Element != null && ReferenceEquals (childRenderer, Platform.GetRenderer (childRenderer.Element))) | ||||||
|  |                         childRenderer.Element.ClearValue (Platform.RendererProperty); | ||||||
|  |                 } | ||||||
|  |                 renderer.NativeView.RemoveChild (subviews[i]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void FillChildrenWithRenderers (VisualElement element) | ||||||
|  |         { | ||||||
|  |             foreach (var logicalChild in ((IElementController)element).LogicalChildren) { | ||||||
|  |                 var child = logicalChild as VisualElement; | ||||||
|  |                 if (child != null) { | ||||||
|  |                     //if (CompressedLayout.GetIsHeadless (child)) { | ||||||
|  |                     //    child.IsPlatformEnabled = true; | ||||||
|  |                     //    FillChildrenWithRenderers (child); | ||||||
|  |                     //} | ||||||
|  |                     //else { | ||||||
|  |                         var renderer = GetFreeRenderer (child) ?? Platform.CreateRenderer (child); | ||||||
|  |                         Platform.SetRenderer (child, renderer); | ||||||
|  |                         _parent.NativeView.AppendChild (renderer.NativeView); | ||||||
|  |                     //} | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void PushRenderer (IVisualElementRenderer renderer) | ||||||
|  |         { | ||||||
|  |             var reflectableType = renderer as System.Reflection.IReflectableType; | ||||||
|  |             var rendererType = reflectableType != null ? reflectableType.GetTypeInfo ().AsType () : renderer.GetType (); | ||||||
|  | 
 | ||||||
|  |             Stack<IVisualElementRenderer> renderers; | ||||||
|  |             if (!_freeRenderers.TryGetValue (rendererType, out renderers)) | ||||||
|  |                 _freeRenderers[rendererType] = renderers = new Stack<IVisualElementRenderer> (); | ||||||
|  | 
 | ||||||
|  |             renderers.Push (renderer); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void UpdateRenderers (Xamarin.Forms.Element newElement) | ||||||
|  |         { | ||||||
|  |             var newElementController = (IElementController)newElement; | ||||||
|  | 
 | ||||||
|  |             if (newElementController.LogicalChildren.Count == 0) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             var subviews = _parent.NativeView.Children; | ||||||
|  |             for (var i = 0; i < subviews.Count; i++) { | ||||||
|  |                 var childRenderer = subviews[i] as IVisualElementRenderer; | ||||||
|  |                 if (childRenderer == null) | ||||||
|  |                     continue; | ||||||
|  | 
 | ||||||
|  |                 var x = (int)childRenderer.NativeView.Style.ZIndex / 1000; | ||||||
|  |                 var element = newElementController.LogicalChildren[x] as VisualElement; | ||||||
|  |                 if (element == null) | ||||||
|  |                     continue; | ||||||
|  | 
 | ||||||
|  |                 if (childRenderer.Element != null && ReferenceEquals (childRenderer, Platform.GetRenderer (childRenderer.Element))) | ||||||
|  |                     childRenderer.Element.ClearValue (Platform.RendererProperty); | ||||||
|  | 
 | ||||||
|  |                 childRenderer.SetElement (element); | ||||||
|  |                 Platform.SetRenderer (element, childRenderer); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -5,6 +5,10 @@ using Xamarin.Forms; | ||||||
| 
 | 
 | ||||||
| namespace Ooui.Forms.Renderers | namespace Ooui.Forms.Renderers | ||||||
| { | { | ||||||
|  |     public abstract class ViewRenderer : ViewRenderer<View, Ooui.Element> | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 	public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement> where TElement : View where TNativeElement : Ooui.Element | 	public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement> where TElement : View where TNativeElement : Ooui.Element | ||||||
| 	{ | 	{ | ||||||
| 		Color _defaultColor; | 		Color _defaultColor; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,182 @@ | ||||||
|  | using System; | ||||||
|  | using Xamarin.Forms; | ||||||
|  | 
 | ||||||
|  | namespace Ooui.Forms | ||||||
|  | { | ||||||
|  |     public class VisualElementPackager : IDisposable | ||||||
|  |     { | ||||||
|  |         VisualElement _element; | ||||||
|  | 
 | ||||||
|  |         bool _isDisposed; | ||||||
|  | 
 | ||||||
|  |         IElementController ElementController => _element; | ||||||
|  | 
 | ||||||
|  |         public VisualElementPackager (IVisualElementRenderer renderer) : this (renderer, null) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         VisualElementPackager (IVisualElementRenderer renderer, VisualElement element) | ||||||
|  |         { | ||||||
|  |             if (renderer == null) | ||||||
|  |                 throw new ArgumentNullException (nameof (renderer)); | ||||||
|  | 
 | ||||||
|  |             Renderer = renderer; | ||||||
|  |             renderer.ElementChanged += OnRendererElementChanged; | ||||||
|  |             SetElement (null, element ?? renderer.Element); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected IVisualElementRenderer Renderer { get; set; } | ||||||
|  | 
 | ||||||
|  |         public void Dispose () | ||||||
|  |         { | ||||||
|  |             Dispose (true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Load () | ||||||
|  |         { | ||||||
|  |             for (var i = 0; i < ElementController.LogicalChildren.Count; i++) { | ||||||
|  |                 var child = ElementController.LogicalChildren[i] as VisualElement; | ||||||
|  |                 if (child != null) | ||||||
|  |                     OnChildAdded (child); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected virtual void Dispose (bool disposing) | ||||||
|  |         { | ||||||
|  |             if (_isDisposed) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             if (disposing) { | ||||||
|  |                 SetElement (_element, null); | ||||||
|  |                 if (Renderer != null) { | ||||||
|  |                     Renderer.ElementChanged -= OnRendererElementChanged; | ||||||
|  |                     Renderer = null; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             _isDisposed = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected virtual void OnChildAdded (VisualElement view) | ||||||
|  |         { | ||||||
|  |             if (_isDisposed) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             //if (CompressedLayout.GetIsHeadless (view)) { | ||||||
|  |             //    var packager = new VisualElementPackager (Renderer, view); | ||||||
|  |             //    view.IsPlatformEnabled = true; | ||||||
|  |             //    packager.Load (); | ||||||
|  |             //} | ||||||
|  |             //else { | ||||||
|  |                 var viewRenderer = Platform.CreateRenderer (view); | ||||||
|  |                 Platform.SetRenderer (view, viewRenderer); | ||||||
|  | 
 | ||||||
|  |                 var uiview = Renderer.NativeView; | ||||||
|  |                 uiview.AppendChild (viewRenderer.NativeView); | ||||||
|  | 
 | ||||||
|  |                 EnsureChildrenOrder (); | ||||||
|  |             //} | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected virtual void OnChildRemoved (VisualElement view) | ||||||
|  |         { | ||||||
|  |             if (_element == null) | ||||||
|  |                 return; | ||||||
|  |              | ||||||
|  |             var viewRenderer = Platform.GetRenderer (view); | ||||||
|  |             if (viewRenderer == null || viewRenderer.NativeView == null) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             var parentRenderer = Platform.GetRenderer (_element); | ||||||
|  |             if (parentRenderer == null || parentRenderer.NativeView == null) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             parentRenderer.NativeView.RemoveChild (viewRenderer.NativeView); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void EnsureChildrenOrder () | ||||||
|  |         { | ||||||
|  |             if (ElementController.LogicalChildren.Count == 0) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             for (var z = 0; z < ElementController.LogicalChildren.Count; z++) { | ||||||
|  |                 var child = ElementController.LogicalChildren[z] as VisualElement; | ||||||
|  |                 if (child == null) | ||||||
|  |                     continue; | ||||||
|  |                 var childRenderer = Platform.GetRenderer (child); | ||||||
|  | 
 | ||||||
|  |                 if (childRenderer == null) | ||||||
|  |                     continue; | ||||||
|  | 
 | ||||||
|  |                 var nativeControl = childRenderer.NativeView; | ||||||
|  | #if __MOBILE__ | ||||||
|  |                 Renderer.NativeView.BringSubviewToFront(nativeControl); | ||||||
|  | #endif | ||||||
|  |                 nativeControl.Style.ZIndex = z * 1000; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void OnChildAdded (object sender, ElementEventArgs e) | ||||||
|  |         { | ||||||
|  |             var view = e.Element as VisualElement; | ||||||
|  |             if (view != null) | ||||||
|  |                 OnChildAdded (view); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void OnChildRemoved (object sender, ElementEventArgs e) | ||||||
|  |         { | ||||||
|  |             var view = e.Element as VisualElement; | ||||||
|  |             if (view != null) | ||||||
|  |                 OnChildRemoved (view); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void OnRendererElementChanged (object sender, VisualElementChangedEventArgs args) | ||||||
|  |         { | ||||||
|  |             if (args.NewElement == _element) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             SetElement (_element, args.NewElement); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void SetElement (VisualElement oldElement, VisualElement newElement) | ||||||
|  |         { | ||||||
|  |             if (oldElement == newElement) | ||||||
|  |                 return; | ||||||
|  | 
 | ||||||
|  |             if (oldElement != null) { | ||||||
|  |                 oldElement.ChildAdded -= OnChildAdded; | ||||||
|  |                 oldElement.ChildRemoved -= OnChildRemoved; | ||||||
|  |                 oldElement.ChildrenReordered -= UpdateChildrenOrder; | ||||||
|  | 
 | ||||||
|  |                 if (newElement != null) { | ||||||
|  |                     var pool = new RendererPool (Renderer, oldElement); | ||||||
|  |                     pool.UpdateNewElement (newElement); | ||||||
|  | 
 | ||||||
|  |                     EnsureChildrenOrder (); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     var elementController = ((IElementController)oldElement); | ||||||
|  | 
 | ||||||
|  |                     for (var i = 0; i < elementController.LogicalChildren.Count; i++) { | ||||||
|  |                         var child = elementController.LogicalChildren[i] as VisualElement; | ||||||
|  |                         if (child != null) | ||||||
|  |                             OnChildRemoved (child); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             _element = newElement; | ||||||
|  | 
 | ||||||
|  |             if (newElement != null) { | ||||||
|  |                 newElement.ChildAdded += OnChildAdded; | ||||||
|  |                 newElement.ChildRemoved += OnChildRemoved; | ||||||
|  |                 newElement.ChildrenReordered += UpdateChildrenOrder; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void UpdateChildrenOrder (object sender, EventArgs e) | ||||||
|  |         { | ||||||
|  |             EnsureChildrenOrder (); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -6,10 +6,17 @@ using Xamarin.Forms; | ||||||
| 
 | 
 | ||||||
| namespace Ooui.Forms | namespace Ooui.Forms | ||||||
| { | { | ||||||
|  |     [Flags] | ||||||
|  |     public enum VisualElementRendererFlags | ||||||
|  |     { | ||||||
|  |         Disposed = 1 << 0, | ||||||
|  |         AutoTrack = 1 << 1, | ||||||
|  |         AutoPackage = 1 << 2 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public class VisualElementRenderer<TElement> : Ooui.Element, IVisualElementRenderer where TElement : VisualElement |     public class VisualElementRenderer<TElement> : Ooui.Element, IVisualElementRenderer where TElement : VisualElement | ||||||
|     { |     { | ||||||
|         bool disposedValue = false; // To detect redundant calls |         bool disposedValue = false; // To detect redundant calls | ||||||
|         VisualElementTracker _tracker; |  | ||||||
| 
 | 
 | ||||||
|         readonly Color _defaultColor = Color.Clear; |         readonly Color _defaultColor = Color.Clear; | ||||||
| 
 | 
 | ||||||
|  | @ -29,6 +36,31 @@ namespace Ooui.Forms | ||||||
|         readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = |         readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = | ||||||
|             new List<EventHandler<VisualElementChangedEventArgs>> (); |             new List<EventHandler<VisualElementChangedEventArgs>> (); | ||||||
| 
 | 
 | ||||||
|  |         VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack; | ||||||
|  | 
 | ||||||
|  |         VisualElementPackager _packager; | ||||||
|  |         VisualElementTracker _tracker; | ||||||
|  | 
 | ||||||
|  |         protected bool AutoPackage { | ||||||
|  |             get { return (_flags & VisualElementRendererFlags.AutoPackage) != 0; } | ||||||
|  |             set { | ||||||
|  |                 if (value) | ||||||
|  |                     _flags |= VisualElementRendererFlags.AutoPackage; | ||||||
|  |                 else | ||||||
|  |                     _flags &= ~VisualElementRendererFlags.AutoPackage; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected bool AutoTrack { | ||||||
|  |             get { return (_flags & VisualElementRendererFlags.AutoTrack) != 0; } | ||||||
|  |             set { | ||||||
|  |                 if (value) | ||||||
|  |                     _flags |= VisualElementRendererFlags.AutoTrack; | ||||||
|  |                 else | ||||||
|  |                     _flags &= ~VisualElementRendererFlags.AutoTrack; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         public VisualElementRenderer () : base ("div") |         public VisualElementRenderer () : base ("div") | ||||||
|         { |         { | ||||||
|             _propertyChangedHandler = OnElementPropertyChanged; |             _propertyChangedHandler = OnElementPropertyChanged; | ||||||
|  | @ -70,10 +102,10 @@ namespace Ooui.Forms | ||||||
|                     _tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget (); |                     _tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget (); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 //if (AutoPackage && _packager == null) { |                 if (AutoPackage && _packager == null) { | ||||||
|                 //	_packager = new VisualElementPackager (this); |                 	_packager = new VisualElementPackager (this); | ||||||
|                 //	_packager.Load (); |                 	_packager.Load (); | ||||||
|                 //} |                 } | ||||||
| 
 | 
 | ||||||
|                 //if (AutoTrack && _events == null) { |                 //if (AutoTrack && _events == null) { | ||||||
|                 //	_events = new EventTracker (this); |                 //	_events = new EventTracker (this); | ||||||
|  |  | ||||||
|  | @ -140,13 +140,9 @@ namespace Ooui.Forms | ||||||
|             parentBoundsChanged = true; |             parentBoundsChanged = true; | ||||||
|             bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); |             bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); | ||||||
|             if (shouldUpdate) { |             if (shouldUpdate) { | ||||||
|                 var visualParent = parent as VisualElement; |  | ||||||
|                 float newY = visualParent == null ? y : Math.Max (0, (float)(visualParent.Height - y - view.Height)); |  | ||||||
|                 var target = new Rectangle (x, newY, width, height); |  | ||||||
| 
 |  | ||||||
|                 uiview.Style.Position = "absolute"; |                 uiview.Style.Position = "absolute"; | ||||||
|                 uiview.Style.Left = x + "px"; |                 uiview.Style.Left = x + "px"; | ||||||
|                 uiview.Style.Top = newY + "px"; |                 uiview.Style.Top = y + "px"; | ||||||
|                 uiview.Style.Width = width + "px"; |                 uiview.Style.Width = width + "px"; | ||||||
|                 uiview.Style.Height = height + "px"; |                 uiview.Style.Height = height + "px"; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -312,6 +312,11 @@ namespace Ooui | ||||||
|             set => this["width"] = value; |             set => this["width"] = value; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         public Value ZIndex { | ||||||
|  |             get => this["z-index"]; | ||||||
|  |             set => this["z-index"] = value; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         public Value this[string propertyName] { |         public Value this[string propertyName] { | ||||||
|             get { |             get { | ||||||
|                 lock (properties) { |                 lock (properties) { | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ namespace Samples | ||||||
| 
 | 
 | ||||||
| 			var countLabel = new Label { | 			var countLabel = new Label { | ||||||
| 				Text = "0", | 				Text = "0", | ||||||
|  |                 BackgroundColor = Color.Gold, | ||||||
| 			}; | 			}; | ||||||
| 			var countButton = new Button { | 			var countButton = new Button { | ||||||
| 			}; | 			}; | ||||||
|  | @ -21,6 +22,7 @@ namespace Samples | ||||||
| 			}; | 			}; | ||||||
| 			var page = new ContentPage { | 			var page = new ContentPage { | ||||||
| 				Content = new StackLayout { | 				Content = new StackLayout { | ||||||
|  |                     BackgroundColor = Color.Khaki, | ||||||
| 					Children = { | 					Children = { | ||||||
| 						new Label { Text = "Hello World!" }, | 						new Label { Text = "Hello World!" }, | ||||||
| 						countLabel, | 						countLabel, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue