diff options
Diffstat (limited to 'Xamarin.Forms.Core/VisualElement.cs')
-rw-r--r-- | Xamarin.Forms.Core/VisualElement.cs | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/VisualElement.cs b/Xamarin.Forms.Core/VisualElement.cs new file mode 100644 index 00000000..16efe730 --- /dev/null +++ b/Xamarin.Forms.Core/VisualElement.cs @@ -0,0 +1,764 @@ +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<Behavior>), typeof(VisualElement), default(IList<Behavior>), + defaultValueCreator: bindable => + { + var collection = new AttachedCollection<Behavior>(); + collection.AttachTo(bindable); + return collection; + }); + + public static readonly BindableProperty BehaviorsProperty = BehaviorsPropertyKey.BindableProperty; + + internal static readonly BindablePropertyKey TriggersPropertyKey = BindableProperty.CreateReadOnly("Triggers", typeof(IList<TriggerBase>), typeof(VisualElement), default(IList<TriggerBase>), + defaultValueCreator: bindable => + { + var collection = new AttachedCollection<TriggerBase>(); + 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<Size, SizeRequest> _measureCache = new Dictionary<Size, SizeRequest>(); + + 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<Behavior> Behaviors + { + get { return (IList<Behavior>)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<TriggerBase> Triggers + { + get { return (IList<TriggerBase>)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<VisualElement>(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<FocusEventArgs> 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) + { + SizeRequest result = GetSizeRequest(widthConstraint, heightConstraint); + + 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<FocusRequestArgs> unfocus = FocusChangeRequested; + if (unfocus != null) + { + unfocus(this, new FocusRequestArgs()); + } + } + + public event EventHandler<FocusEventArgs> 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) + { + return OnSizeRequest(widthConstraint, heightConstraint); + } + + protected override void OnParentSet() + { + base.OnParentSet(); + + if (ParentView != null) + { + NavigationProxy.Inner = ParentView.NavigationProxy; + } + else + { + NavigationProxy.Inner = null; + } + } + + 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<EventArg<VisualElement>> 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<FocusRequestArgs> 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<KeyValuePair<string, object>> values) + { + if (values == null) + return; + + if (Resources == null || Resources.Count == 0) + { + base.OnParentResourcesChanged(values); + return; + } + + var innerKeys = new HashSet<string>(); + var changedResources = new List<KeyValuePair<string, object>>(); + foreach (KeyValuePair<string, object> c in Resources) + innerKeys.Add(c.Key); + foreach (KeyValuePair<string, object> value in values) + { + if (innerKeys.Add(value.Key)) + changedResources.Add(value); + else if (value.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal)) + { + var mergedClassStyles = new List<Style>(Resources[value.Key] as List<Style>); + mergedClassStyles.AddRange(value.Value as List<Style>); + changedResources.Add(new KeyValuePair<string, object>(value.Key, mergedClassStyles)); + } + } + if (changedResources.Count != 0) + OnResourcesChanged(changedResources); + } + + internal void UnmockBounds() + { + _mockX = _mockY = _mockWidth = _mockHeight = -1; + } + + void OnFocused() + { + EventHandler<FocusEventArgs> focus = Focused; + if (focus != null) + focus(this, new FocusEventArgs(this, true)); + } + + static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + var element = bindable as VisualElement; + + var isFocused = (bool)newvalue; + if (isFocused) + { + element.OnFocused(); + } + else + { + element.OnUnfocus(); + } + } + + static void OnRequestChanged(BindableObject bindable, object oldvalue, object newvalue) + { + var constraint = LayoutConstraint.None; + var element = (VisualElement)bindable; + if (element.WidthRequest >= 0 && element.MinimumWidthRequest >= 0) + { + constraint |= LayoutConstraint.HorizontallyFixed; + } + if (element.HeightRequest >= 0 && element.MinimumHeightRequest >= 0) + { + constraint |= LayoutConstraint.VerticallyFixed; + } + + element.SelfConstraint = constraint; + ((VisualElement)bindable).InvalidateMeasure(InvalidationTrigger.SizeRequestChanged); + } + + void OnUnfocus() + { + EventHandler<FocusEventArgs> unFocus = Unfocused; + if (unFocus != null) + unFocus(this, new FocusEventArgs(this, false)); + } + + void SetSize(double width, double height) + { + if (Width == width && Height == height) + return; + + Width = width; + Height = height; + + SizeAllocated(width, height); + if (SizeChanged != null) + SizeChanged(this, EventArgs.Empty); + } + + internal class FocusRequestArgs : EventArgs + { + public bool Focus { get; set; } + + public bool Result { get; set; } + } + } +}
\ No newline at end of file |