using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ComponentModel;
using ElmSharp;
using EColor = ElmSharp.Color;
using ESize = ElmSharp.Size;
using ERect = ElmSharp.Rect;
using ERectangle = ElmSharp.Rectangle;
using Specific = Xamarin.Forms.PlatformConfiguration.TizenSpecific.VisualElement;
namespace Xamarin.Forms.Platform.Tizen
{
///
/// Base class for rendering of a Xamarin element.
///
public abstract class VisualElementRenderer : IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement
{
///
/// Holds registered element changed handlers.
///
readonly List> _elementChangedHandlers = new List>();
///
/// Handler for property changed events.
///
PropertyChangedEventHandler _propertyChangedHandler;
EventHandler> _batchCommittedHandler;
///
/// Flags which control status of renderer.
///
VisualElementRendererFlags _flags = VisualElementRendererFlags.None;
///
/// Holds the native view.
///
EvasObject _view;
Dictionary> _propertyHandlersWithInit = new Dictionary>();
Dictionary _propertyHandlers = new Dictionary();
HashSet _batchedProperties = new HashSet();
///
/// Default constructor.
///
protected VisualElementRenderer()
{
RegisterPropertyHandler(VisualElement.IsVisibleProperty, UpdateIsVisible);
RegisterPropertyHandler(VisualElement.OpacityProperty, UpdateOpacity);
RegisterPropertyHandler(VisualElement.IsEnabledProperty, UpdateIsEnabled);
RegisterPropertyHandler(VisualElement.InputTransparentProperty, UpdateInputTransparent);
RegisterPropertyHandler(VisualElement.BackgroundColorProperty, UpdateBackgroundColor);
RegisterPropertyHandler(Specific.StyleProperty, UpdateThemeStyle);
RegisterPropertyHandler(VisualElement.AnchorXProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.AnchorYProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.ScaleProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.RotationProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.RotationXProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.RotationYProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.TranslationXProperty, ApplyTransformation);
RegisterPropertyHandler(VisualElement.TranslationYProperty, ApplyTransformation);
}
~VisualElementRenderer()
{
Dispose(false);
}
event EventHandler ElementChanged
{
add
{
_elementChangedHandlers.Add(value);
}
remove
{
_elementChangedHandlers.Remove(value);
}
}
///
/// Gets the Xamarin element associated with this renderer.
///
public TElement Element
{
get;
private set;
}
VisualElement IVisualElementRenderer.Element
{
get
{
return this.Element;
}
}
public EvasObject NativeView
{
get
{
return _view;
}
}
protected bool IsDisposed => (_flags == VisualElementRendererFlags.Disposed);
///
/// Releases all resource used by the object.
///
/// Call when you are finished using the
/// . The method
/// leaves the in an unusable state.
/// After calling , you must release all references to the
/// so the garbage collector can reclaim
/// the memory that the was occupying.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
if (null == NativeView)
{
return new SizeRequest(new Size(0, 0));
}
else
{
int availableWidth = Forms.ConvertToScaledPixel(widthConstraint);
int availableHeight = Forms.ConvertToScaledPixel(heightConstraint);
if (availableWidth < 0)
availableWidth = int.MaxValue;
if (availableHeight < 0)
availableHeight = int.MaxValue;
Size measured;
var nativeViewMeasurable = NativeView as Native.IMeasurable;
if (nativeViewMeasurable != null)
{
measured = nativeViewMeasurable.Measure(availableWidth, availableHeight).ToDP();
}
else
{
measured = Measure(availableWidth, availableHeight).ToDP();
}
return new SizeRequest(measured, MinimumSize());
}
}
///
/// Sets the element associated with this renderer.
///
public void SetElement(TElement newElement)
{
if (newElement == null)
{
throw new ArgumentNullException("newElement");
}
TElement oldElement = Element;
if (oldElement != null)
{
throw new InvalidOperationException("oldElement");
}
Element = newElement;
if (_propertyChangedHandler == null)
{
_propertyChangedHandler = new PropertyChangedEventHandler(OnElementPropertyChanged);
}
if (_batchCommittedHandler == null)
{
_batchCommittedHandler = OnBatchCommitted;
}
// send notification
OnElementChanged(new ElementChangedEventArgs(oldElement, newElement));
// store renderer for the new element
Platform.SetRenderer(newElement, this);
// add children
var logicalChildren = (newElement as IElementController).LogicalChildren;
foreach (Element child in logicalChildren)
{
AddChild(child);
}
OnElementReady();
}
public void UpdateNativeGeometry()
{
var x = ComputeAbsoluteX(Element);
var y = ComputeAbsoluteY(Element);
NativeView.Geometry = new Rectangle(x, y, Element.Width, Element.Height).ToPixel();
ApplyTransformation();
}
void IVisualElementRenderer.SetElement(VisualElement element)
{
TElement tElement = element as TElement;
if (tElement == null)
{
throw new ArgumentException("Element is not of type " + typeof(TElement), "Element");
}
SetElement(tElement);
}
///
/// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform.
///
/// The effect to register.
void IEffectControlProvider.RegisterEffect(Effect effect)
{
RegisterEffect(effect);
}
///
/// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform.
///
/// The effect to register.
protected void RegisterEffect(Effect effect)
{
var platformEffect = effect as PlatformEffect;
if (platformEffect != null)
{
OnRegisterEffect(platformEffect);
}
}
protected virtual void UpdateLayout()
{
// we're updating the coordinates of native control only if they were modified
// via Xamarin (Settings.IgnoreBatchCommitted is set to false);
// otherwise native control is already in the right place
if (!Settings.IgnoreBatchCommitted && null != NativeView)
{
UpdateNativeGeometry();
}
// we're updating just immediate children
var logicalChildren = (Element as IElementController).LogicalChildren;
foreach (var child in logicalChildren)
{
Platform.GetRenderer(child)?.UpdateNativeGeometry();
}
}
///
/// Disposes of underlying resources.
///
/// True if the memory release was requested on demand.
protected virtual void Dispose(bool disposing)
{
if ((_flags & VisualElementRendererFlags.Disposed) != 0)
{
return;
}
_flags |= VisualElementRendererFlags.Disposed;
if (disposing)
{
if (Element != null)
{
Element.PropertyChanged -= _propertyChangedHandler;
Element.BatchCommitted -= _batchCommittedHandler;
Element.ChildAdded -= OnChildAdded;
Element.ChildRemoved -= OnChildRemoved;
Element.ChildrenReordered -= OnChildrenReordered;
Element.FocusChangeRequested -= OnFocusChangeRequested;
Settings.StartIgnoringBatchCommitted();
Element.Layout(new Rectangle(0, 0, -1, -1));
Settings.StopIgnoringBatchCommitted();
var logicalChildren = (Element as IElementController).LogicalChildren;
foreach (var child in logicalChildren)
{
Platform.GetRenderer(child)?.Dispose();
}
if (Platform.GetRenderer(Element) == this)
{
Platform.SetRenderer(Element, (IVisualElementRenderer)null);
}
Element = default(TElement);
}
if (NativeView != null)
{
NativeView.Deleted -= NativeViewDeleted;
NativeView.Unrealize();
SetNativeView(null);
}
}
}
///
/// Notification that the associated element has changed.
///
/// Event parameters.
protected virtual void OnElementChanged(ElementChangedEventArgs e)
{
if (null != e.OldElement)
{
e.OldElement.PropertyChanged -= _propertyChangedHandler;
e.OldElement.BatchCommitted -= _batchCommittedHandler;
e.OldElement.ChildAdded -= OnChildAdded;
e.OldElement.ChildRemoved -= OnChildRemoved;
e.OldElement.ChildrenReordered -= OnChildrenReordered;
e.OldElement.FocusChangeRequested -= OnFocusChangeRequested;
Settings.StartIgnoringBatchCommitted();
Element.Layout(new Rectangle(0, 0, -1, -1));
Settings.StopIgnoringBatchCommitted();
var controller = e.OldElement as IElementController;
if (controller != null && controller.EffectControlProvider == this)
{
controller.EffectControlProvider = null;
}
}
if (null != e.NewElement)
{
e.NewElement.PropertyChanged += _propertyChangedHandler;
e.NewElement.BatchCommitted += _batchCommittedHandler;
e.NewElement.ChildAdded += OnChildAdded;
e.NewElement.ChildRemoved += OnChildRemoved;
e.NewElement.ChildrenReordered += OnChildrenReordered;
e.NewElement.FocusChangeRequested += OnFocusChangeRequested;
UpdateAllProperties(true);
var controller = e.NewElement as IElementController;
if (controller != null)
{
controller.EffectControlProvider = this;
}
}
// TODO: handle the event
}
///
/// Notification that the property of the associated element has changed.
///
/// Object which sent the notification.
/// Event parameters.
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Element.Batched)
{
if (e.PropertyName == VisualElement.XProperty.PropertyName ||
e.PropertyName == VisualElement.YProperty.PropertyName ||
e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
{
_flags |= VisualElementRendererFlags.NeedsLayout;
}
else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName ||
e.PropertyName == VisualElement.TranslationYProperty.PropertyName ||
e.PropertyName == VisualElement.RotationProperty.PropertyName ||
e.PropertyName == VisualElement.RotationXProperty.PropertyName ||
e.PropertyName == VisualElement.RotationYProperty.PropertyName ||
e.PropertyName == VisualElement.ScaleProperty.PropertyName ||
e.PropertyName == VisualElement.AnchorXProperty.PropertyName ||
e.PropertyName == VisualElement.AnchorYProperty.PropertyName)
{
_flags |= VisualElementRendererFlags.NeedsTransformation;
}
else
{
_batchedProperties.Add(e.PropertyName);
}
return;
}
Action init;
if (_propertyHandlersWithInit.TryGetValue(e.PropertyName, out init))
{
init(false);
}
else
{
Action handler;
if (_propertyHandlers.TryGetValue(e.PropertyName, out handler))
{
handler();
}
}
}
///
/// Updates the attached event handlers, sets the native control.
///
protected void SetNativeControl(EvasObject control)
{
if (NativeView != null)
{
NativeView.Moved -= OnMoved;
NativeView.Deleted -= NativeViewDeleted;
}
Widget widget = NativeView as Widget;
if (widget != null)
{
widget.Focused -= OnFocused;
widget.Unfocused -= OnUnfocused;
}
SetNativeView(control);
if (NativeView != null)
{
NativeView.Deleted += NativeViewDeleted;
NativeView.Moved += OnMoved;
}
widget = NativeView as Widget;
if (widget != null)
{
widget.Focused += OnFocused;
widget.Unfocused += OnUnfocused;
}
}
void NativeViewDeleted(object sender, EventArgs e)
{
Dispose();
}
void OnBatchCommitted(object sender, EventArg e)
{
if (_flags.HasFlag(VisualElementRendererFlags.NeedsLayout))
{
if (!Settings.IgnoreBatchCommitted)
{
UpdateLayout();
// UpdateLayout already updates transformation, clear NeedsTranformation flag then
_flags &= ~VisualElementRendererFlags.NeedsTransformation;
}
_flags ^= VisualElementRendererFlags.NeedsLayout;
}
if (_flags.HasFlag(VisualElementRendererFlags.NeedsTransformation))
{
ApplyTransformation();
_flags ^= VisualElementRendererFlags.NeedsTransformation;
}
foreach (string property in _batchedProperties)
{
OnElementPropertyChanged(this, new PropertyChangedEventArgs(property));
}
_batchedProperties.Clear();
}
///
/// Registers a handler which is executed when specified property changes.
///
/// Handled property.
/// Action to be executed when property changes.
protected void RegisterPropertyHandler(BindableProperty property, Action handler)
{
RegisterPropertyHandler(property.PropertyName, handler);
}
///
/// Registers a handler which is executed when specified property changes.
///
/// Name of the handled property.
/// Action to be executed when property changes.
protected void RegisterPropertyHandler(string name, Action handler)
{
_propertyHandlersWithInit.Add(name, handler);
}
///
/// Registers a handler which is executed when specified property changes.
///
/// Handled property.
/// Action to be executed when property changes.
protected void RegisterPropertyHandler(BindableProperty property, Action handler)
{
RegisterPropertyHandler(property.PropertyName, handler);
}
///
/// Registers a handler which is executed when specified property changes.
///
/// Name of the handled property.
/// Action to be executed when property changes.
protected void RegisterPropertyHandler(string name, Action handler)
{
_propertyHandlers.Add(name, handler);
}
///
/// Updates all registered properties.
///
/// If set to true the method is called for an uninitialized object.
protected void UpdateAllProperties(bool initialization)
{
foreach (KeyValuePair> kvp in _propertyHandlersWithInit)
{
kvp.Value(initialization);
}
foreach (KeyValuePair kvp in _propertyHandlers)
{
kvp.Value();
}
}
///
/// Called when Element has been set and its native counterpart
/// is properly initialized.
///
protected virtual void OnElementReady()
{
}
protected void DoLayout(Native.LayoutEventArgs e)
{
Settings.StartIgnoringBatchCommitted();
Element.Layout(new Rectangle(Element.X, Element.Y, Forms.ConvertToScaledDP(e.Width), Forms.ConvertToScaledDP(e.Height)));
if (e.HasChanged)
{
UpdateLayout();
}
Settings.StopIgnoringBatchCommitted();
}
protected virtual Size MinimumSize()
{
return new Size();
}
///
/// Calculates how much space this element should take, given how much room there is.
///
/// a desired dimensions of the element
protected virtual ESize Measure(int availableWidth, int availableHeight)
{
return new ESize(NativeView.MinimumWidth, NativeView.MinimumHeight);
}
protected virtual void UpdateBackgroundColor()
{
if (NativeView is Widget)
{
(NativeView as Widget).BackgroundColor = Element.BackgroundColor.ToNative();
}
else
{
Log.Warn("{0} uses {1} which does not support background color", this, NativeView);
}
}
protected virtual void UpdateOpacity()
{
if (NativeView is Widget)
{
(NativeView as Widget).Opacity = (int)(Element.Opacity * 255.0);
}
else
{
Log.Warn("{0} uses {1} which does not support opacity", this, NativeView);
}
}
static double ComputeAbsoluteX(VisualElement e)
{
return e.X + (e.RealParent is VisualElement ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).NativeView.Geometry.X) : 0.0);
}
static double ComputeAbsoluteY(VisualElement e)
{
return e.Y + (e.RealParent is VisualElement ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).NativeView.Geometry.Y) : 0.0);
}
///
/// Handles focus events.
///
void OnFocused(object sender, EventArgs e)
{
if (null != Element)
{
Element.SetValue(VisualElement.IsFocusedPropertyKey, true);
}
}
///
/// Handles unfocus events.
///
void OnUnfocused(object sender, EventArgs e)
{
if (null != Element)
{
Element.SetValue(VisualElement.IsFocusedPropertyKey, false);
}
}
///
/// Sets the native control, updates the control's properties.
///
void SetNativeView(EvasObject control)
{
_view = control;
}
///
/// Adds a new child if it's derived from the VisualElement class. Otherwise this method does nothing.
///
/// Child to be added.
void AddChild(Element child)
{
VisualElement vElement = child as VisualElement;
if (vElement != null)
{
var childRenderer = Platform.GetOrCreateRenderer(vElement);
// if the native view can have children, attach the new child
if (NativeView is Native.IContainable)
{
(NativeView as Native.IContainable).Children.Add(childRenderer.NativeView);
}
}
}
void RemoveChild(VisualElement view)
{
var renderer = Platform.GetRenderer(view);
var containerObject = NativeView as Native.IContainable;
if (containerObject != null)
{
containerObject.Children.Remove(renderer.NativeView);
}
renderer.Dispose();
}
void OnChildAdded(object sender, ElementEventArgs e)
{
var view = e.Element as VisualElement;
if (view != null)
{
AddChild(view);
}
// changing the order makes sense only in case of Layouts
if (Element is Layout)
{
IElementController controller = Element as IElementController;
if (controller.LogicalChildren[controller.LogicalChildren.Count - 1] != view)
{
EnsureChildOrder();
}
}
}
void OnChildRemoved(object sender, ElementEventArgs e)
{
var view = e.Element as VisualElement;
if (view != null)
{
RemoveChild(view);
}
}
void OnChildrenReordered(object sender, EventArgs e)
{
EnsureChildOrder();
Layout layout = Element as Layout;
if (layout != null)
{
layout.InvalidateMeasureInternal(Internals.InvalidationTrigger.MeasureChanged);
layout.ForceLayout();
}
}
void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
{
Widget widget = NativeView as Widget;
if (widget == null)
{
Log.Warn("{0} is not a widget, it cannot receive focus", NativeView);
return;
}
widget.SetFocus(e.Focus);
e.Result = true;
}
///
/// On register the effect
///
/// The effect to register.
void OnRegisterEffect(PlatformEffect effect)
{
effect.Container = Element.Parent == null ? null : Platform.GetRenderer(Element.Parent).NativeView;
effect.Control = NativeView;
}
void OnMoved(object sender, EventArgs e)
{
ApplyTransformation();
}
void EnsureChildOrder()
{
var logicalChildren = (Element as IElementController).LogicalChildren;
for (var i = logicalChildren.Count - 1; i >= 0; --i)
{
var element = logicalChildren[i] as VisualElement;
if (element != null)
{
Platform.GetRenderer(element).NativeView?.Lower();
}
}
}
void UpdateIsVisible()
{
if (null != NativeView)
{
if (Element.IsVisible)
{
NativeView.Show();
}
else
{
NativeView.Hide();
}
}
}
///
/// Updates the IsEnabled property.
///
void UpdateIsEnabled()
{
var widget = NativeView as Widget;
if (widget != null)
{
widget.IsEnabled = Element.IsEnabled;
}
}
///
/// Updates the InputTransparent property.
///
void UpdateInputTransparent()
{
NativeView.PassEvents = Element.InputTransparent;
}
protected virtual void UpdateThemeStyle()
{
}
void ApplyRotation(EvasMap map, ERect geometry, ref bool changed)
{
var rotationX = Element.RotationX;
var rotationY = Element.RotationY;
var rotationZ = Element.Rotation;
var anchorX = Element.AnchorX;
var anchorY = Element.AnchorY;
// apply rotations
if (rotationX != 0 || rotationY != 0 || rotationZ != 0)
{
map.Rotate3D(rotationX, rotationY, rotationZ, (int)(geometry.X + geometry.Width * anchorX),
(int)(geometry.Y + geometry.Height * anchorY), 0);
changed = true;
}
}
void ApplyScale(EvasMap map, ERect geometry, ref bool changed)
{
var scale = Element.Scale;
// apply scale factor
if (scale != 1.0)
{
map.Zoom(scale, scale,
geometry.X + (int)(geometry.Width * Element.AnchorX),
geometry.Y + (int)(geometry.Height * Element.AnchorY));
changed = true;
}
}
void ApplyTranslation(EvasMap map, ERect geometry, ref bool changed)
{
var shiftX = Forms.ConvertToScaledPixel(Element.TranslationX);
var shiftY = Forms.ConvertToScaledPixel(Element.TranslationY);
// apply translation, i.e. move/shift the object a little
if (shiftX != 0 || shiftY != 0)
{
if (changed)
{
// special care is taken to apply the translation last
Point3D p;
for (int i = 0; i < 4; i++)
{
p = map.GetPointCoordinate(i);
p.X += shiftX;
p.Y += shiftY;
map.SetPointCoordinate(i, p);
}
}
else
{
// in case when we only need translation, then construct the map in a simpler way
geometry.X += shiftX;
geometry.Y += shiftY;
map.PopulatePoints(geometry, 0);
changed = true;
}
}
}
protected virtual void ApplyTransformation()
{
if (null == NativeView)
{
Log.Error("Trying to apply transformation to the non-existent native control");
return;
}
// prepare the EFL effect structure
ERect geometry = NativeView.Geometry;
EvasMap map = new EvasMap(4);
map.PopulatePoints(geometry, 0);
bool changed = false;
ApplyRotation(map, geometry, ref changed);
ApplyScale(map, geometry, ref changed);
ApplyTranslation(map, geometry, ref changed);
NativeView.IsMapEnabled = changed;
if (changed)
{
NativeView.EvasMap = map;
}
}
}
internal static class Settings
{
static int s_ignoreCount = 0;
public static bool IgnoreBatchCommitted
{
get
{
return s_ignoreCount != 0;
}
}
public static void StartIgnoringBatchCommitted()
{
++s_ignoreCount;
}
public static void StopIgnoringBatchCommitted()
{
Debug.Assert(s_ignoreCount > 0);
--s_ignoreCount;
}
}
}