using System; using System.Collections.Generic; namespace Xamarin.Forms { public partial class VisualElement : Element, IAnimatable, IVisualElementController, IResourcesProvider { internal static readonly BindablePropertyKey NavigationPropertyKey = BindableProperty.CreateReadOnly("Navigation", typeof(INavigation), typeof(VisualElement), default(INavigation)); public static readonly BindableProperty NavigationProperty = NavigationPropertyKey.BindableProperty; public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create("InputTransparent", typeof(bool), typeof(VisualElement), default(bool)); public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(VisualElement), true); static readonly BindablePropertyKey XPropertyKey = BindableProperty.CreateReadOnly("X", typeof(double), typeof(VisualElement), default(double)); public static readonly BindableProperty XProperty = XPropertyKey.BindableProperty; static readonly BindablePropertyKey YPropertyKey = BindableProperty.CreateReadOnly("Y", typeof(double), typeof(VisualElement), default(double)); public static readonly BindableProperty YProperty = YPropertyKey.BindableProperty; public static readonly BindableProperty AnchorXProperty = BindableProperty.Create("AnchorX", typeof(double), typeof(VisualElement), .5d); public static readonly BindableProperty AnchorYProperty = BindableProperty.Create("AnchorY", typeof(double), typeof(VisualElement), .5d); public static readonly BindableProperty TranslationXProperty = BindableProperty.Create("TranslationX", typeof(double), typeof(VisualElement), 0d); public static readonly BindableProperty TranslationYProperty = BindableProperty.Create("TranslationY", typeof(double), typeof(VisualElement), 0d); static readonly BindablePropertyKey WidthPropertyKey = BindableProperty.CreateReadOnly("Width", typeof(double), typeof(VisualElement), -1d, coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value); public static readonly BindableProperty WidthProperty = WidthPropertyKey.BindableProperty; static readonly BindablePropertyKey HeightPropertyKey = BindableProperty.CreateReadOnly("Height", typeof(double), typeof(VisualElement), -1d, coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value); public static readonly BindableProperty HeightProperty = HeightPropertyKey.BindableProperty; public static readonly BindableProperty RotationProperty = BindableProperty.Create("Rotation", typeof(double), typeof(VisualElement), default(double)); public static readonly BindableProperty RotationXProperty = BindableProperty.Create("RotationX", typeof(double), typeof(VisualElement), default(double)); public static readonly BindableProperty RotationYProperty = BindableProperty.Create("RotationY", typeof(double), typeof(VisualElement), default(double)); public static readonly BindableProperty ScaleProperty = BindableProperty.Create("Scale", typeof(double), typeof(VisualElement), 1d); public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create("IsVisible", typeof(bool), typeof(VisualElement), true, propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue)); public static readonly BindableProperty OpacityProperty = BindableProperty.Create("Opacity", typeof(double), typeof(VisualElement), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1)); public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create("BackgroundColor", typeof(Color), typeof(VisualElement), Color.Default); internal static readonly BindablePropertyKey BehaviorsPropertyKey = BindableProperty.CreateReadOnly("Behaviors", typeof(IList), typeof(VisualElement), default(IList), defaultValueCreator: bindable => { var collection = new AttachedCollection(); collection.AttachTo(bindable); return collection; }); public static readonly BindableProperty BehaviorsProperty = BehaviorsPropertyKey.BindableProperty; internal static readonly BindablePropertyKey TriggersPropertyKey = BindableProperty.CreateReadOnly("Triggers", typeof(IList), typeof(VisualElement), default(IList), defaultValueCreator: bindable => { var collection = new AttachedCollection(); collection.AttachTo(bindable); return collection; }); public static readonly BindableProperty TriggersProperty = TriggersPropertyKey.BindableProperty; public static readonly BindableProperty StyleProperty = BindableProperty.Create("Style", typeof(Style), typeof(VisualElement), default(Style), propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.Style = (Style)newvalue); public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create("WidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create("HeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create("MinimumWidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create("MinimumHeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); internal static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused", typeof(bool), typeof(VisualElement), default(bool), propertyChanged: OnIsFocusedPropertyChanged); public static readonly BindableProperty IsFocusedProperty = IsFocusedPropertyKey.BindableProperty; readonly Dictionary _measureCache = new Dictionary(); readonly MergedStyle _mergedStyle; int _batched; LayoutConstraint _computedConstraint; bool _isInNativeLayout; bool _isNativeStateConsistent = true; bool _isPlatformEnabled; double _mockHeight = -1; double _mockWidth = -1; double _mockX = -1; double _mockY = -1; ResourceDictionary _resources; LayoutConstraint _selfConstraint; internal VisualElement() { Navigation = new NavigationProxy(); _mergedStyle = new MergedStyle(GetType(), this); } public double AnchorX { get { return (double)GetValue(AnchorXProperty); } set { SetValue(AnchorXProperty, value); } } public double AnchorY { get { return (double)GetValue(AnchorYProperty); } set { SetValue(AnchorYProperty, value); } } public Color BackgroundColor { get { return (Color)GetValue(BackgroundColorProperty); } set { SetValue(BackgroundColorProperty, value); } } public IList Behaviors { get { return (IList)GetValue(BehaviorsProperty); } } public Rectangle Bounds { get { return new Rectangle(X, Y, Width, Height); } private set { if (value.X == X && value.Y == Y && value.Height == Height && value.Width == Width) return; BatchBegin(); X = value.X; Y = value.Y; SetSize(value.Width, value.Height); BatchCommit(); } } public double Height { get { return _mockHeight == -1 ? (double)GetValue(HeightProperty) : _mockHeight; } private set { SetValue(HeightPropertyKey, value); } } public double HeightRequest { get { return (double)GetValue(HeightRequestProperty); } set { SetValue(HeightRequestProperty, value); } } public bool InputTransparent { get { return (bool)GetValue(InputTransparentProperty); } set { SetValue(InputTransparentProperty, value); } } public bool IsEnabled { get { return (bool)GetValue(IsEnabledProperty); } set { SetValue(IsEnabledProperty, value); } } public bool IsFocused { get { return (bool)GetValue(IsFocusedProperty); } } public bool IsVisible { get { return (bool)GetValue(IsVisibleProperty); } set { SetValue(IsVisibleProperty, value); } } public double MinimumHeightRequest { get { return (double)GetValue(MinimumHeightRequestProperty); } set { SetValue(MinimumHeightRequestProperty, value); } } public double MinimumWidthRequest { get { return (double)GetValue(MinimumWidthRequestProperty); } set { SetValue(MinimumWidthRequestProperty, value); } } public INavigation Navigation { get { return (INavigation)GetValue(NavigationProperty); } internal set { SetValue(NavigationPropertyKey, value); } } public double Opacity { get { return (double)GetValue(OpacityProperty); } set { SetValue(OpacityProperty, value); } } public double Rotation { get { return (double)GetValue(RotationProperty); } set { SetValue(RotationProperty, value); } } public double RotationX { get { return (double)GetValue(RotationXProperty); } set { SetValue(RotationXProperty, value); } } public double RotationY { get { return (double)GetValue(RotationYProperty); } set { SetValue(RotationYProperty, value); } } public double Scale { get { return (double)GetValue(ScaleProperty); } set { SetValue(ScaleProperty, value); } } public Style Style { get { return (Style)GetValue(StyleProperty); } set { SetValue(StyleProperty, value); } } public string StyleClass { get { return _mergedStyle.StyleClass; } set { _mergedStyle.StyleClass = value; } } public double TranslationX { get { return (double)GetValue(TranslationXProperty); } set { SetValue(TranslationXProperty, value); } } public double TranslationY { get { return (double)GetValue(TranslationYProperty); } set { SetValue(TranslationYProperty, value); } } public IList Triggers { get { return (IList)GetValue(TriggersProperty); } } public double Width { get { return _mockWidth == -1 ? (double)GetValue(WidthProperty) : _mockWidth; } private set { SetValue(WidthPropertyKey, value); } } public double WidthRequest { get { return (double)GetValue(WidthRequestProperty); } set { SetValue(WidthRequestProperty, value); } } public double X { get { return _mockX == -1 ? (double)GetValue(XProperty) : _mockX; } private set { SetValue(XPropertyKey, value); } } public double Y { get { return _mockY == -1 ? (double)GetValue(YProperty) : _mockY; } private set { SetValue(YPropertyKey, value); } } internal bool Batched { get { return _batched > 0; } } internal LayoutConstraint ComputedConstraint { get { return _computedConstraint; } set { if (_computedConstraint == value) return; LayoutConstraint oldConstraint = Constraint; _computedConstraint = value; LayoutConstraint newConstraint = Constraint; if (oldConstraint != newConstraint) OnConstraintChanged(oldConstraint, newConstraint); } } internal LayoutConstraint Constraint { get { return ComputedConstraint | SelfConstraint; } } internal bool DisableLayout { get; set; } internal bool IsInNativeLayout { get { if (_isInNativeLayout) return true; Element parent = RealParent; while (parent != null) { var visualElement = parent as VisualElement; if (visualElement != null && visualElement.IsInNativeLayout) return true; parent = parent.RealParent; } return false; } set { _isInNativeLayout = value; } } internal bool IsNativeStateConsistent { get { return _isNativeStateConsistent; } set { if (_isNativeStateConsistent == value) return; _isNativeStateConsistent = value; if (value && IsPlatformEnabled) InvalidateMeasure(InvalidationTrigger.RendererReady); } } internal bool IsPlatformEnabled { get { return _isPlatformEnabled; } set { if (value == _isPlatformEnabled) return; _isPlatformEnabled = value; if (value && IsNativeStateConsistent) InvalidateMeasure(InvalidationTrigger.RendererReady); OnIsPlatformEnabledChanged(); } } internal NavigationProxy NavigationProxy { get { return Navigation as NavigationProxy; } } internal LayoutConstraint SelfConstraint { get { return _selfConstraint; } set { if (_selfConstraint == value) return; LayoutConstraint oldConstraint = Constraint; _selfConstraint = value; LayoutConstraint newConstraint = Constraint; if (oldConstraint != newConstraint) { OnConstraintChanged(oldConstraint, newConstraint); } } } public void BatchBegin() { _batched++; } public void BatchCommit() { _batched = Math.Max(0, _batched - 1); if (!Batched && BatchCommitted != null) BatchCommitted(this, new EventArg(this)); } public ResourceDictionary Resources { get { return _resources; } set { if (_resources == value) return; OnPropertyChanging(); if (_resources != null) ((IResourceDictionary)_resources).ValuesChanged -= OnResourcesChanged; _resources = value; OnResourcesChanged(value); if (_resources != null) ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged; OnPropertyChanged(); } } void IVisualElementController.NativeSizeChanged() { InvalidateMeasure(InvalidationTrigger.MeasureChanged); } public event EventHandler ChildrenReordered; public bool Focus() { if (IsFocused) return true; if (FocusChangeRequested == null) return false; var arg = new FocusRequestArgs { Focus = true }; FocusChangeRequested(this, arg); return arg.Result; } public event EventHandler Focused; [Obsolete("Use Measure")] public virtual SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint) { SizeRequest cachedResult; var constraintSize = new Size(widthConstraint, heightConstraint); if (_measureCache.TryGetValue(constraintSize, out cachedResult)) { return cachedResult; } double widthRequest = WidthRequest; double heightRequest = HeightRequest; if (widthRequest >= 0) widthConstraint = Math.Min(widthConstraint, widthRequest); if (heightRequest >= 0) heightConstraint = Math.Min(heightConstraint, heightRequest); SizeRequest result = OnMeasure(widthConstraint, heightConstraint); bool hasMinimum = result.Minimum != result.Request; Size request = result.Request; Size minimum = result.Minimum; if (heightRequest != -1) { request.Height = heightRequest; if (!hasMinimum) minimum.Height = heightRequest; } if (widthRequest != -1) { request.Width = widthRequest; if (!hasMinimum) minimum.Width = widthRequest; } double minimumHeightRequest = MinimumHeightRequest; double minimumWidthRequest = MinimumWidthRequest; if (minimumHeightRequest != -1) minimum.Height = minimumHeightRequest; if (minimumWidthRequest != -1) minimum.Width = minimumWidthRequest; minimum.Height = Math.Min(request.Height, minimum.Height); minimum.Width = Math.Min(request.Width, minimum.Width); var r = new SizeRequest(request, minimum); if (r.Request.Width > 0 && r.Request.Height > 0) { _measureCache[constraintSize] = r; } return r; } public void Layout(Rectangle bounds) { Bounds = bounds; } public SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None) { #pragma warning disable 0618 // retain until GetSizeRequest removed SizeRequest result = GetSizeRequest(widthConstraint, heightConstraint); #pragma warning restore if ((flags & MeasureFlags.IncludeMargins) != 0) { Thickness margin = default(Thickness); var view = this as View; if (view != null) margin = view.Margin; if (!margin.IsDefault) { result.Minimum = new Size(result.Minimum.Width + margin.HorizontalThickness, result.Minimum.Height + margin.VerticalThickness); result.Request = new Size(result.Request.Width + margin.HorizontalThickness, result.Request.Height + margin.VerticalThickness); } } return result; } public event EventHandler MeasureInvalidated; public event EventHandler SizeChanged; public void Unfocus() { if (!IsFocused) return; EventHandler unfocus = FocusChangeRequested; if (unfocus != null) { unfocus(this, new FocusRequestArgs()); } } public event EventHandler Unfocused; protected virtual void InvalidateMeasure() { InvalidateMeasure(InvalidationTrigger.MeasureChanged); } protected override void OnChildAdded(Element child) { base.OnChildAdded(child); var view = child as View; if (view != null) ComputeConstraintForView(view); } protected override void OnChildRemoved(Element child) { base.OnChildRemoved(child); var view = child as View; if (view != null) view.ComputedConstraint = LayoutConstraint.None; } protected void OnChildrenReordered() { if (ChildrenReordered != null) ChildrenReordered(this, EventArgs.Empty); } protected virtual SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { #pragma warning disable 0618 // retain until OnSizeRequest removed return OnSizeRequest(widthConstraint, heightConstraint); #pragma warning restore } protected override void OnParentSet() { #pragma warning disable 0618 // retain until ParentView removed base.OnParentSet(); if (ParentView != null) { NavigationProxy.Inner = ParentView.NavigationProxy; } else { NavigationProxy.Inner = null; } #pragma warning restore } protected virtual void OnSizeAllocated(double width, double height) { } [Obsolete("Use OnMeasure")] protected virtual SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) { if (Platform == null || !IsPlatformEnabled) { return new SizeRequest(new Size(-1, -1)); } return Platform.GetNativeSize(this, widthConstraint, heightConstraint); } protected void SizeAllocated(double width, double height) { OnSizeAllocated(width, height); } internal event EventHandler> BatchCommitted; internal void ComputeConstrainsForChildren() { for (var i = 0; i < LogicalChildren.Count; i++) { var child = LogicalChildren[i] as View; if (child != null) ComputeConstraintForView(child); } } internal virtual void ComputeConstraintForView(View view) { view.ComputedConstraint = LayoutConstraint.None; } internal event EventHandler FocusChangeRequested; internal virtual void InvalidateMeasure(InvalidationTrigger trigger) { _measureCache.Clear(); MeasureInvalidated?.Invoke(this, new InvalidationEventArgs(trigger)); } internal void MockBounds(Rectangle bounds) { _mockX = bounds.X; _mockY = bounds.Y; _mockWidth = bounds.Width; _mockHeight = bounds.Height; } internal virtual void OnConstraintChanged(LayoutConstraint oldConstraint, LayoutConstraint newConstraint) { ComputeConstrainsForChildren(); } internal virtual void OnIsPlatformEnabledChanged() { } internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue) { InvalidateMeasure(InvalidationTrigger.Undefined); } internal override void OnParentResourcesChanged(IEnumerable> values) { if (values == null) return; if (Resources == null || Resources.Count == 0) { base.OnParentResourcesChanged(values); return; } var innerKeys = new HashSet(); var changedResources = new List>(); foreach (KeyValuePair c in Resources) innerKeys.Add(c.Key); foreach (KeyValuePair value in values) { if (innerKeys.Add(value.Key)) changedResources.Add(value); else if (value.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal)) { var mergedClassStyles = new List