From 05c3e94665105c52a4446652ebe6f8f270f0c9be Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Fri, 6 Jan 2017 01:32:54 +0000 Subject: [PATCH] =?UTF-8?q?[csharp]=C2=A0Add=20YogaKit=20shared=20implemen?= =?UTF-8?q?tation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Facebook.YogaKit.Shared.shproj | 11 + .../Facebook.YogaKit.projitems | 15 ++ csharp/Facebook.YogaKit/YogaKit.cs | 180 +++++++++++++ csharp/Facebook.YogaKit/YogaKitNative.cs | 255 ++++++++++++++++++ 4 files changed, 461 insertions(+) create mode 100644 csharp/Facebook.YogaKit/Facebook.YogaKit.Shared.shproj create mode 100644 csharp/Facebook.YogaKit/Facebook.YogaKit.projitems create mode 100644 csharp/Facebook.YogaKit/YogaKit.cs create mode 100644 csharp/Facebook.YogaKit/YogaKitNative.cs diff --git a/csharp/Facebook.YogaKit/Facebook.YogaKit.Shared.shproj b/csharp/Facebook.YogaKit/Facebook.YogaKit.Shared.shproj new file mode 100644 index 00000000..0943de36 --- /dev/null +++ b/csharp/Facebook.YogaKit/Facebook.YogaKit.Shared.shproj @@ -0,0 +1,11 @@ + + + + {A24B3BA6-3143-4FFF-B8B8-1EDF166F5F4A} + + + + + + + \ No newline at end of file diff --git a/csharp/Facebook.YogaKit/Facebook.YogaKit.projitems b/csharp/Facebook.YogaKit/Facebook.YogaKit.projitems new file mode 100644 index 00000000..abc6371b --- /dev/null +++ b/csharp/Facebook.YogaKit/Facebook.YogaKit.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {A24B3BA6-3143-4FFF-B8B8-1EDF166F5F4A} + + + Facebook.YogaKit + + + + + + \ No newline at end of file diff --git a/csharp/Facebook.YogaKit/YogaKit.cs b/csharp/Facebook.YogaKit/YogaKit.cs new file mode 100644 index 00000000..e5d06656 --- /dev/null +++ b/csharp/Facebook.YogaKit/YogaKit.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using Facebook.Yoga; +#if __IOS__ +using NativeView = UIKit.UIView; +#endif + +namespace Facebook.YogaKit +{ + public static class YogaKit + { + internal static Dictionary Bridges = new Dictionary(); + + public static void UsesYoga(this NativeView view, bool usesYoga) + { + YogaKitNative.UsesYoga(view, usesYoga); + } + + public static bool GetUsesYoga(this NativeView view) + { + return YogaKitNative.GetUsesYoga(view); + } + + public static void IncludeYogaLayout(this NativeView view, bool includeInLayout) + { + YogaKitNative.IncludeYogaLayout(view, includeInLayout); + } + + public static bool GetIncludeYogaLayout(this NativeView view) + { + return YogaKitNative.GetIncludeYogaLayout(view); + } + + public static void YogaWidth(this NativeView view, nfloat width) + { + var node = GetYogaNode(view); + node.Width = (float)width; + } + + public static void YogaHeight(this NativeView view, nfloat height) + { + var node = GetYogaNode(view); + node.Height = (float)height; + } + + public static void YogaMinWidth(this NativeView view, float minWidth) + { + var node = GetYogaNode(view); + node.MinWidth = minWidth; + } + + public static void YogaMinHeight(this NativeView view, float minHeight) + { + var node = GetYogaNode(view); + node.MinHeight = minHeight; + } + + public static void YogaMaxWidth(this NativeView view, float maxWidth) + { + var node = GetYogaNode(view); + node.MaxWidth = maxWidth; + } + + public static void YogaMaxHeight(this NativeView view, float maxHeight) + { + var node = GetYogaNode(view); + node.MaxHeight = maxHeight; + } + + public static void YogaAlignItems(this NativeView view, YogaAlign align) + { + var node = GetYogaNode(view); + node.AlignItems = align; + } + + public static void YogaJustify(this NativeView view, YogaJustify justify) + { + var node = GetYogaNode(view); + node.JustifyContent = justify; + } + + public static void YogaAlign(this NativeView view, YogaAlign align) + { + var node = GetYogaNode(view); + node.AlignContent = align; + } + + public static void YogaAlignSelf(this NativeView view, YogaAlign align) + { + var node = GetYogaNode(view); + node.AlignSelf = align; + } + + public static void YogaDirection(this NativeView view, YogaDirection direction) + { + var node = GetYogaNode(view); + node.StyleDirection = direction; + } + + public static void YogaFlexDirection(this NativeView view, YogaFlexDirection direction) + { + var node = GetYogaNode(view); + node.FlexDirection = direction; + } + + public static void YogaPositionType(this NativeView view, YogaPositionType position) + { + var node = GetYogaNode(view); + node.PositionType = position; + } + + public static void YogaFlexWrap(this NativeView view, YogaWrap wrap) + { + var node = GetYogaNode(view); + node.Wrap = wrap; + } + + public static void YogaFlexShrink(this NativeView view, float shrink) + { + var node = GetYogaNode(view); + node.FlexShrink = shrink; + } + + public static void YogaFlexGrow(this NativeView view, float grow) + { + var node = GetYogaNode(view); + node.FlexGrow = grow; + } + + public static void YogaFlexBasis(this NativeView view, float basis) + { + var node = GetYogaNode(view); + node.FlexBasis = basis; + } + + public static void YogaPositionForEdge(this NativeView view, float position, YogaEdge edge) + { + var node = GetYogaNode(view); + node.SetPosition(edge, position); + } + + public static void YogaMarginForEdge(this NativeView view, float margin, YogaEdge edge) + { + var node = GetYogaNode(view); + node.SetMargin(edge, margin); + } + + public static void YogaPaddingForEdge(this NativeView view, float padding, YogaEdge edge) + { + var node = GetYogaNode(view); + node.SetPadding(edge, padding); + } + + public static void YogaAspectRation(this NativeView view, float ratio) + { + var node = GetYogaNode(view); + node.StyleAspectRatio = ratio; + } + + #region Layout and Sizing + public static void YogaApplyLayout(this NativeView view) + { + YogaKitNative.CalculateLayoutWithSize(view, view.Bounds.Size); + YogaKitNative.ApplyLayoutToViewHierarchy(view); + } + + public static YogaDirection YogaResolvedDirection(this NativeView view) + { + var node = GetYogaNode(view); + return node.LayoutDirection; + } + + #endregion + + public static YogaNode GetYogaNode(this NativeView view) + { + return YogaKitNative.GetYogaNode(view); + } + } +} \ No newline at end of file diff --git a/csharp/Facebook.YogaKit/YogaKitNative.cs b/csharp/Facebook.YogaKit/YogaKitNative.cs new file mode 100644 index 00000000..fd872835 --- /dev/null +++ b/csharp/Facebook.YogaKit/YogaKitNative.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Facebook.Yoga; +#if __IOS__ +using CoreGraphics; +using Foundation; +using UIKit; +#endif + +namespace Facebook.YogaKit +{ + internal static class YogaKitNative + { + static NSString YogaNodeKey = new NSString("YogaNode"); + + static NSString UsesYogaKey = new NSString("UsesYoga"); + + static NSString IncludeYogaKey = new NSString("UsesYoga"); + + public static void UsesYoga(UIView view, bool usesYoga) + { + var value = NSNumber.FromBoolean(usesYoga); + objc_setAssociatedObject(view.Handle, UsesYogaKey.Handle, value.Handle, AssociationPolicy.RETAIN_NONATOMIC); + } + + public static bool GetUsesYoga(UIView view) + { + var value = ObjCRuntime.Runtime.GetNSObject(objc_getAssociatedObject(view.Handle, UsesYogaKey.Handle)) as NSNumber; + return value == null ? false : value.BoolValue; + } + + public static void IncludeYogaLayout(UIView view, bool includeInLayout) + { + var value = NSNumber.FromBoolean(includeInLayout); + objc_setAssociatedObject(view.Handle, IncludeYogaKey.Handle, value.Handle, AssociationPolicy.RETAIN_NONATOMIC); + } + + public static bool GetIncludeYogaLayout(UIView view) + { + var value = ObjCRuntime.Runtime.GetNSObject(objc_getAssociatedObject(view.Handle, IncludeYogaKey.Handle)) as NSNumber; + return value == null ? true : value.BoolValue; + } + + public static YogaNode GetYogaNode(UIView view) + { + var value = ObjCRuntime.Runtime.GetNSObject(objc_getAssociatedObject(view.Handle, YogaNodeKey.Handle)) as YGNodeBridge; + if (value == null) + { + value = new YGNodeBridge(); + value.SetContext(view); + objc_setAssociatedObject(view.Handle, YogaNodeKey.Handle, value.Handle, AssociationPolicy.RETAIN_NONATOMIC); + } + return value.node; + } + + public static CGSize CalculateLayoutWithSize(UIView view, CGSize size) + { + if (!view.GetUsesYoga()) + { + System.Diagnostics.Debug.WriteLine("Doesn't use Yoga"); + } + AttachNodesFromViewHierachy(view); + var node = GetYogaNode(view); + + node.Width = (float)size.Width; + node.Height = (float)size.Height; + node.CalculateLayout(); + + return new CGSize { Width = node.LayoutWidth, Height = node.LayoutHeight }; + } + + public static void ApplyLayoutToViewHierarchy(UIView view) + { + if (!view.GetIncludeYogaLayout()) + return; + + var node = GetYogaNode(view); + CGPoint topLeft = new CGPoint(node.LayoutX, node.LayoutY); + CGPoint bottomRight = new CGPoint(topLeft.X + node.LayoutWidth, topLeft.Y + node.LayoutHeight); + view.Frame = new CGRect(RoundPixelValue(topLeft.X), RoundPixelValue(topLeft.Y), RoundPixelValue(bottomRight.X) - RoundPixelValue(topLeft.X), RoundPixelValue(bottomRight.Y) - RoundPixelValue(topLeft.Y)); + bool isLeaf = !view.GetUsesYoga() || view.Subviews.Length == 0; + if (!isLeaf) + { + for (int i = 0; i < view.Subviews.Length; i++) + { + ApplyLayoutToViewHierarchy(view.Subviews[i]); + } + } + } + + public static CGSize YogaIntrinsicSize(this UIView view) + { + var constrainedSize = new CGSize + { + Width = float.NaN, + Height = float.NaN + }; + return CalculateLayoutWithSize(view, constrainedSize); + } + + static YogaSize MeasureView(YogaNode node, float width, YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode) + { + var constrainedWidth = (widthMode == YogaMeasureMode.Undefined) ? nfloat.MaxValue : width; + var constrainedHeight = (heightMode == YogaMeasureMode.Undefined) ? nfloat.MaxValue : height; + + UIView view = null; + if (YogaKit.Bridges.ContainsKey(node)) + (YogaKit.Bridges[node] as YGNodeBridge).viewRef.TryGetTarget(out view); + + var sizeThatFits = view.SizeThatFits(new CGSize(constrainedWidth, constrainedHeight)); + + var finalWidth = SanitizeMeasurement(constrainedWidth, sizeThatFits.Width, widthMode); + var finalHeight = SanitizeMeasurement(constrainedHeight, sizeThatFits.Height, heightMode); + + return MeasureOutput.Make(finalWidth, finalHeight); + } + + static float SanitizeMeasurement(nfloat constrainedSize, nfloat measuredSize, YogaMeasureMode measureMode) + { + float result; + if (measureMode == YogaMeasureMode.Exactly) + { + result = (float)constrainedSize; + } + else if (measureMode == YogaMeasureMode.AtMost) + { + result = (float)Math.Min(constrainedSize, measuredSize); + } + else { + result = (float)measuredSize; + } + + return result; + + } + + static double RoundPixelValue(nfloat value) + { + nfloat scale = UIScreen.MainScreen.Scale; + + return Math.Round(value * scale) / scale; + } + + static void AttachNodesFromViewHierachy(UIView view) + { + var node = GetYogaNode(view); + + // Only leaf nodes should have a measure function + if (!view.GetUsesYoga() || view.Subviews.Length == 0) + { + node.SetMeasureFunction(MeasureView); + RemoveAllChildren(node); + } + else + { + node.SetMeasureFunction(null); + // Create a list of all the subviews that we are going to use for layout. + var subviewsToInclude = new List(); + foreach (var subview in view.Subviews) + { + if (subview.GetIncludeYogaLayout()) + { + subviewsToInclude.Add(subview); + } + } + + var shouldReconstructChildList = false; + if (node.Count != subviewsToInclude.Count) + { + shouldReconstructChildList = true; + } + else + { + for (int i = 0; i < subviewsToInclude.Count; i++) + { + if (node[i] != GetYogaNode(subviewsToInclude[i])) + { + shouldReconstructChildList = true; + break; + } + } + } + + if (shouldReconstructChildList) + { + RemoveAllChildren(node); + + for (int i = 0; i < subviewsToInclude.Count; i++) + { + var subView = subviewsToInclude[i]; + node.Insert(i, subView.GetYogaNode()); + AttachNodesFromViewHierachy(subView); + } + } + } + } + + static void RemoveAllChildren(YogaNode node) + { + if (node == null) + return; + + while (node.Count > 0) + { + node.Clear(); + } + } + + + [DllImport("/usr/lib/libobjc.dylib")] + static extern void objc_setAssociatedObject(IntPtr @object, IntPtr key, IntPtr value, AssociationPolicy policy); + + [DllImport("/usr/lib/libobjc.dylib")] + static extern IntPtr objc_getAssociatedObject(IntPtr @object, IntPtr key); + + enum AssociationPolicy + { + ASSIGN = 0, + RETAIN_NONATOMIC = 1, + COPY_NONATOMIC = 3, + RETAIN = 01401, + COPY = 01403, + } + } + + class YGNodeBridge : NSObject + { + bool disposed; + internal WeakReference viewRef; + internal YogaNode node; + public YGNodeBridge() + { + node = new YogaNode(); + } + + public void SetContext(UIView view) + { + viewRef = new WeakReference(view); + YogaKit.Bridges.Add(node, this); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !disposed) + { + disposed = true; + YogaKit.Bridges.Remove(node); + viewRef = null; + node = null; + } + base.Dispose(disposing); + } + } +} \ No newline at end of file