From 498a5980e8f6d58632ebfc91371ae29bcc9727b9 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Sun, 15 Jan 2017 14:50:08 -0800 Subject: [PATCH] Add YogaKit Shared and iOS version Summary: Using shared code for reuse in other platforms based on iOS native implementation. Adds YogaKit sample. Adds YogaKit tests (same as objc). ``` YogaKitTest : 80 ms Facebook.YogaKit.iOS.Tests.exe : 81 ms Tests run: 11 Passed: 8 Inconclusive: 0 Failed: 3 Ignored: 1 ``` Since we don't have extension properties we need to go with a extension method to get access to the YogaLayout . I m also not sure this is leak free yet, would love some help with testing and feedback about view/node lifecycle Closes https://github.com/facebook/yoga/pull/336 Reviewed By: splhack Differential Revision: D4415027 Pulled By: emilsjolander fbshipit-source-id: c88328212426c3200e6f0c48cda594cd2c432065 --- .../Facebook.YogaKit.Shared.shproj | 11 + .../Facebook.YogaKit.projitems | 16 + csharp/Facebook.YogaKit/IYogaLayout.cs | 85 ++ csharp/Facebook.YogaKit/YogaKit.cs | 10 + csharp/Facebook.YogaKit/YogaLayout.cs | 888 ++++++++++++++++++ .../AppDelegate.cs | 23 + .../AppIcon.appiconset/Contents.json | 157 ++++ .../Assets.xcassets/Contents.json | 6 + .../Entitlements.plist | 6 + .../Facebook.YogaKit.iOS.Sample.csproj | 128 +++ .../Facebook.YogaKit.iOS.Sample/Info.plist | 39 + .../LaunchScreen.storyboard | 27 + .../iOS/Facebook.YogaKit.iOS.Sample/Main.cs | 12 + .../Main.storyboard | 25 + .../ViewController.cs | 54 ++ .../ViewController.designer.cs | 17 + .../Entitlements.plist | 6 + .../Facebook.YogaKit.iOS.Tests.csproj | 118 +++ .../iOS/Facebook.YogaKit.iOS.Tests/Info.plist | 36 + .../LaunchScreen.storyboard | 27 + csharp/iOS/Facebook.YogaKit.iOS.Tests/Main.cs | 20 + .../UnitTestAppDelegate.cs | 45 + csharp/iOS/Facebook.YogaKit.iOS.sln | 84 ++ .../Facebook.YogaKit.iOS.csproj | 69 ++ .../Properties/AssemblyInfo.cs | 26 + csharp/iOS/Facebook.YogaKit.iOS/YogaKit.cs | 43 + csharp/iOS/Facebook.YogaKit.iOS/YogaLayout.cs | 52 + .../Facebook.YogaKit.Shared.Tests.projitems | 14 + .../Facebook.YogaKit.Shared.Tests.shproj | 11 + .../YogaKitTest.cs | 369 ++++++++ 30 files changed, 2424 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/IYogaLayout.cs create mode 100644 csharp/Facebook.YogaKit/YogaKit.cs create mode 100644 csharp/Facebook.YogaKit/YogaLayout.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/AppDelegate.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/Contents.json create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Entitlements.plist create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Facebook.YogaKit.iOS.Sample.csproj create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Info.plist create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/LaunchScreen.storyboard create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.storyboard create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.designer.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/Entitlements.plist create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/Facebook.YogaKit.iOS.Tests.csproj create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/Info.plist create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/LaunchScreen.storyboard create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/Main.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.Tests/UnitTestAppDelegate.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS.sln create mode 100644 csharp/iOS/Facebook.YogaKit.iOS/Facebook.YogaKit.iOS.csproj create mode 100644 csharp/iOS/Facebook.YogaKit.iOS/Properties/AssemblyInfo.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS/YogaKit.cs create mode 100644 csharp/iOS/Facebook.YogaKit.iOS/YogaLayout.cs create mode 100644 csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.projitems create mode 100644 csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.shproj create mode 100644 csharp/tests/Facebook.YogaKit.Shared.Tests/YogaKitTest.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..af8db327 --- /dev/null +++ b/csharp/Facebook.YogaKit/Facebook.YogaKit.projitems @@ -0,0 +1,16 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {A24B3BA6-3143-4FFF-B8B8-1EDF166F5F4A} + + + Facebook.YogaKit + + + + + + + \ No newline at end of file diff --git a/csharp/Facebook.YogaKit/IYogaLayout.cs b/csharp/Facebook.YogaKit/IYogaLayout.cs new file mode 100644 index 00000000..721c19ce --- /dev/null +++ b/csharp/Facebook.YogaKit/IYogaLayout.cs @@ -0,0 +1,85 @@ +using System; +using System.Drawing; +using Facebook.Yoga; + +namespace Facebook.YogaKit +{ + public interface IYogaLayout : IDisposable + { + bool IsEnabled { get; set; } + bool IsIncludeInLayout { get; set; } + + //YogaDirection Direction { get; set; } + YogaFlexDirection FlexDirection { get; set; } + YogaJustify JustifyContent { get; set; } + YogaAlign AlignContent { get; set; } + YogaAlign AlignSelf { get; set; } + YogaAlign AlignItems { get; set; } + YogaPositionType Position { get; set; } + YogaWrap FlexWrap { get; set; } + YogaOverflow Overflow { get; set; } + + + float FlexGrow { get; set; } + float FlexShrink { get; set; } + float FlexBasis { get; set; } + + float Left { get; set; } + float Top { get; set; } + float Right { get; set; } + float Bottom { get; set; } + float Start { get; set; } + float End { get; set; } + + float MarginLeft { get; set; } + float MarginTop { get; set; } + float MarginRight { get; set; } + float MarginBottom { get; set; } + float MarginStart { get; set; } + float MarginEnd { get; set; } + float MarginHorizontal { get; set; } + float MarginVertical { get; set; } + float Margin { get; set; } + + float PaddingLeft { get; set; } + float PaddingTop { get; set; } + float PaddingRight { get; set; } + float PaddingBottom { get; set; } + float PaddingStart { get; set; } + float PaddingEnd { get; set; } + float PaddingHorizontal { get; set; } + float PaddingVertical { get; set; } + float Padding { get; set; } + + float BorderLeftWidth { get; set; } + float BorderTopWidth { get; set; } + float BorderRightWidth { get; set; } + float BorderBottomWidth { get; set; } + float BorderStartWidth { get; set; } + float BorderEndWidth { get; set; } + float BorderWidth { get; set; } + + float Width { get; set; } + float Height { get; set; } + float MinWidth { get; set; } + float MinHeight { get; set; } + float MaxWidth { get; set; } + float MaxHeight { get; set; } + + // Yoga specific properties, not compatible with flexbox specification + + // Returns the size of the view if no constraints were given. This could equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + SizeF IntrinsicSize { get; } + float AspectRatio { get; set; } + // Get the resolved direction of this node. This won't be YGDirectionInherit + YogaDirection ResolvedDirection { get; } + // Returns the number of children that are using Flexbox. + int NumberOfChildren { get; } + // Return a BOOL indiciating whether or not we this node contains any subviews that are included in Yoga's layout. + bool IsLeaf { get; } + // Perform a layout calculation and update the frames of the views in the hierarchy with the results + void ApplyLayout(); + // Mark that a view's layout needs to be recalculated. Only works for leaf views. + void MarkDirty(); + } +} diff --git a/csharp/Facebook.YogaKit/YogaKit.cs b/csharp/Facebook.YogaKit/YogaKit.cs new file mode 100644 index 00000000..7bdae48c --- /dev/null +++ b/csharp/Facebook.YogaKit/YogaKit.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Facebook.Yoga; + +namespace Facebook.YogaKit +{ + public static partial class YogaKit + { + internal static Dictionary Bridges = new Dictionary(); + } +} diff --git a/csharp/Facebook.YogaKit/YogaLayout.cs b/csharp/Facebook.YogaKit/YogaLayout.cs new file mode 100644 index 00000000..f4cd4635 --- /dev/null +++ b/csharp/Facebook.YogaKit/YogaLayout.cs @@ -0,0 +1,888 @@ +using System; +using Facebook.Yoga; +using System.Collections.Generic; +using System.Drawing; +#if __IOS__ +using NativeView = UIKit.UIView; +#endif + +namespace Facebook.YogaKit +{ + public partial class YogaLayout : IYogaLayout + { + WeakReference _viewRef; + YogaNode _node; + + internal YogaLayout(NativeView view) + { + _viewRef = new WeakReference(view); + _node = new YogaNode(); + YogaKit.Bridges.Add(_node, view); + + IsEnabled = false; + IsIncludeInLayout = true; + } + + public new void MarkDirty() + { + if (IsLeaf) + _node.MarkDirty(); + } + + public bool IsLeaf + { + get + { + if (IsEnabled) + { + NativeView view = null; + if (_viewRef.TryGetTarget(out view)) + { + foreach (NativeView subview in GetChildren(view)) + { + var layout = subview.Yoga(); + if (layout.IsEnabled && layout.IsIncludeInLayout) + { + return false; + } + } + } + + } + return true; + } + } + + + + public bool IsEnabled + { + get; + set; + } + + public bool IsIncludeInLayout + { + get; + set; + } + + public int NumberOfChildren => _node.Count; + + public YogaDirection ResolvedDirection => _node.LayoutDirection; + + public YogaFlexDirection FlexDirection + { + get + { + return _node.FlexDirection; + } + + set + { + _node.FlexDirection = value; + } + } + + public YogaAlign AlignItems + { + get + { + return _node.AlignItems; + } + + set + { + _node.AlignItems = value; + } + } + + public YogaJustify JustifyContent + { + get + { + return _node.JustifyContent; + } + + set + { + _node.JustifyContent = value; + } + } + + public YogaAlign AlignContent + { + get + { + return _node.AlignContent; + } + + set + { + _node.AlignContent = value; + } + } + + public YogaAlign AlignSelf + { + get + { + return _node.AlignSelf; + } + + set + { + _node.AlignSelf = value; + } + } + + public YogaPositionType Position + { + get + { + return _node.PositionType; + } + + set + { + _node.PositionType = value; + } + } + + public YogaWrap FlexWrap + { + get + { + return _node.Wrap; + } + + set + { + _node.Wrap = value; + } + } + + public YogaOverflow Overflow + { + get + { + return _node.Overflow; + } + + set + { + _node.Overflow = value; + } + } + + public float FlexGrow + { + get + { + return _node.FlexGrow; + } + + set + { + _node.FlexGrow = value; + } + } + + public float FlexShrink + { + get + { + return _node.FlexShrink; + } + + set + { + _node.FlexShrink = value; + } + } + + public float FlexBasis + { + get + { + return (float)_node.FlexBasis.Value; + } + + set + { + _node.FlexBasis = value; + } + } + + public float Left + { + get + { + return _node.Left.Value; + } + + set + { + _node.Left = value; + } + } + + public float Top + { + get + { + return _node.Top.Value; + } + + set + { + _node.Top = value; + } + } + + public float Right + { + get + { + return _node.Right.Value; + } + + set + { + _node.Right = value; + } + } + + public float Bottom + { + get + { + return _node.Bottom.Value; + } + + set + { + _node.Bottom = value; + } + } + + public float Start + { + get + { + return _node.Start.Value; + } + + set + { + _node.Start = value; + } + } + + public float End + { + get + { + return _node.End.Value; + } + + set + { + _node.End = value; + } + } + + public float MarginLeft + { + get + { + return _node.MarginLeft.Value; + } + + set + { + _node.MarginLeft = value; + } + } + + public float MarginTop + { + get + { + return _node.MarginTop.Value; + } + + set + { + _node.MarginTop = value; + } + } + + public float MarginRight + { + get + { + return _node.MarginRight.Value; + } + + set + { + _node.MarginRight = value; + } + } + + public float MarginBottom + { + get + { + return _node.MarginBottom.Value; + } + + set + { + _node.MarginBottom = value; + } + } + + public float MarginStart + { + get + { + return _node.MarginStart.Value; + } + + set + { + _node.MarginStart = value; + } + } + + public float MarginEnd + { + get + { + return _node.MarginEnd.Value; + } + + set + { + _node.MarginEnd = value; + } + } + + public float MarginHorizontal + { + get + { + return _node.MarginHorizontal.Value; + } + + set + { + _node.MarginHorizontal = value; + } + } + + public float MarginVertical + { + get + { + return _node.MarginVertical.Value; + } + + set + { + _node.MarginVertical = value; + } + } + + public float Margin + { + get + { + return _node.Margin.Value; + } + + set + { + _node.Margin = value; + } + } + + public float PaddingLeft + { + get + { + return _node.PaddingLeft.Value; + } + + set + { + _node.PaddingLeft = value; + } + } + + public float PaddingTop + { + get + { + return _node.PaddingTop.Value; + } + + set + { + _node.PaddingTop = value; + } + } + + public float PaddingRight + { + get + { + return _node.PaddingRight.Value; + } + + set + { + _node.PaddingRight = value; + } + } + + public float PaddingBottom + { + get + { + return _node.PaddingBottom.Value; + } + + set + { + _node.PaddingBottom = value; + } + } + + public float PaddingStart + { + get + { + return _node.PaddingStart.Value; + } + + set + { + _node.PaddingStart = value; + } + } + + public float PaddingEnd + { + get + { + return _node.PaddingEnd.Value; + } + + set + { + _node.PaddingEnd = value; + } + } + + public float PaddingHorizontal + { + get + { + return _node.PaddingHorizontal.Value; + } + + set + { + _node.PaddingHorizontal = value; + } + } + + public float PaddingVertical + { + get + { + return _node.PaddingVertical.Value; + } + + set + { + _node.PaddingHorizontal = value; + } + } + + public float Padding + { + get + { + return _node.Padding.Value; + } + + set + { + _node.Padding = value; + } + } + + public float BorderLeftWidth + { + get + { + return _node.BorderLeftWidth; + } + + set + { + _node.BorderLeftWidth = value; + } + } + + public float BorderTopWidth + { + get + { + return _node.BorderTopWidth; + } + + set + { + _node.BorderTopWidth = value; + } + } + + public float BorderRightWidth + { + get + { + return _node.BorderRightWidth; + } + + set + { + _node.BorderRightWidth = value; + } + } + + public float BorderBottomWidth + { + get + { + return _node.BorderBottomWidth; + } + + set + { + _node.BorderBottomWidth = value; + } + } + + public float BorderStartWidth + { + get + { + return _node.BorderStartWidth; + } + + set + { + _node.BorderStartWidth = value; + } + } + + public float BorderEndWidth + { + get + { + return _node.BorderEndWidth; + } + + set + { + _node.BorderEndWidth = value; + } + } + + public float BorderWidth + { + get + { + return _node.BorderWidth; + } + + set + { + _node.BorderWidth = value; + } + } + + public float Height + { + get + { + return _node.Height.Value; + } + + set + { + _node.Height = value; + } + } + + public float Width + { + get + { + return _node.Width.Value; + } + + set + { + _node.Width = value; + } + } + + public float MinWidth + { + get + { + return _node.MinWidth.Value; + } + + set + { + _node.MinWidth = value; + } + } + + public float MinHeight + { + get + { + return _node.MinHeight.Value; + } + + set + { + _node.MinHeight = value; + } + } + + public float MaxWidth + { + get + { + return _node.MaxWidth.Value; + } + + set + { + _node.MaxWidth = value; + } + } + + public float MaxHeight + { + get + { + return _node.MaxHeight.Value; + } + + set + { + _node.MaxHeight = value; + } + } + + public float AspectRatio + { + get + { + return _node.StyleAspectRatio; + } + + set + { + _node.StyleAspectRatio = value; + } + } + + public void ApplyLayout() + { + NativeView view = null; + if (_viewRef.TryGetTarget(out view)) + { + float width = 0; + float height = 0; + GetWidthHeightOfNativeView(view, out width, out height); + CalculateLayoutWithSize(this, width, height); + ApplyLayoutToViewHierarchy(view); + } + } + public SizeF IntrinsicSize + { + get + { + return CalculateLayoutWithSize(this, float.NaN, float.NaN); + } + } + + SizeF CalculateLayoutWithSize(YogaLayout layout, float width, float height) + { + //TODO : Check thread access + if (!layout.IsEnabled) + { + System.Diagnostics.Debug.WriteLine("Doesn't use Yoga"); + } + NativeView view = null; + if (_viewRef.TryGetTarget(out view)) + { + AttachNodesFromViewHierachy(view); + } + + var node = layout._node; + + node.Width = width; + node.Height = height; + node.CalculateLayout(); + + return new SizeF { Width = node.LayoutWidth, Height = node.LayoutHeight }; + } + + static YogaSize MeasureView(YogaNode node, float width, YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode) + { + var constrainedWidth = (widthMode == YogaMeasureMode.Undefined) ? float.MaxValue : width; + var constrainedHeight = (heightMode == YogaMeasureMode.Undefined) ? float.MaxValue : height; + + NativeView view = null; + if (YogaKit.Bridges.ContainsKey(node)) + view = YogaKit.Bridges[node] as NativeView; + + float sizeThatFitsWidth = 0; + float sizeThatFitsHeight = 0; + + MeasureNativeView(view, constrainedWidth, constrainedHeight, out sizeThatFitsWidth, out sizeThatFitsHeight); + + var finalWidth = SanitizeMeasurement(constrainedWidth, sizeThatFitsWidth, widthMode); + var finalHeight = SanitizeMeasurement(constrainedHeight, sizeThatFitsHeight, heightMode); + + return MeasureOutput.Make(finalWidth, finalHeight); + } + + static float SanitizeMeasurement(float constrainedSize, float 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 bool NodeHasExactSameChildren(YogaNode node, NativeView[] subviews) + { + if (node.Count != subviews.Length) + return false; + for (int i = 0; i < subviews.Length; i++) + { + YogaLayout yoga = subviews[i].Yoga() as YogaLayout; + if (node[i] != yoga._node) + { + return false; + } + } + return true; + } + + static void AttachNodesFromViewHierachy(NativeView view) + { + YogaLayout yoga = view.Yoga() as YogaLayout; + var node = yoga._node; + // Only leaf nodes should have a measure function + if (yoga.IsLeaf) + { + RemoveAllChildren(node); + node.SetMeasureFunction(MeasureView); + } + 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.Yoga().IsIncludeInLayout) + { + subviewsToInclude.Add(subview); + } + } + + if (!NodeHasExactSameChildren(node, subviewsToInclude.ToArray())) + { + RemoveAllChildren(node); + for (int i = 0; i < subviewsToInclude.Count; i++) + { + YogaLayout yogaSubview = subviewsToInclude[i].Yoga() as YogaLayout; + node.Insert(i, yogaSubview._node); + } + } + + foreach (var subView in subviewsToInclude) + { + AttachNodesFromViewHierachy(subView); + } + } + } + + static void RemoveAllChildren(YogaNode node) + { + if (node == null) + return; + + if (node.Count > 0) + { + node.Clear(); + } + } + + static double RoundPixelValue(float value) + { + float scale = NativePixelScale; + + return Math.Round(value * scale) / scale; + } + + static void ApplyLayoutToViewHierarchy(NativeView view) + { + //TODO : "Framesetting should only be done on the main thread." + YogaLayout yoga = view.Yoga() as YogaLayout; + + if (!yoga.IsIncludeInLayout) + return; + + var node = yoga._node; + + ApplyLayoutToNativeView(view, node); + + if (!yoga.IsLeaf) + { + for (int i = 0; i < view.Subviews.Length; i++) + { + ApplyLayoutToViewHierarchy(view.Subviews[i]); + } + } + } + + + } +} + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/AppDelegate.cs b/csharp/iOS/Facebook.YogaKit.iOS.Sample/AppDelegate.cs new file mode 100644 index 00000000..ce187082 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/AppDelegate.cs @@ -0,0 +1,23 @@ +using Foundation; +using UIKit; + +namespace Facebook.YogaKit.iOS.Sample +{ + [Register("AppDelegate")] + public class AppDelegate : UIApplicationDelegate + { + public override UIWindow Window + { + get; + set; + } + + public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + { + return true; + } + + + } +} + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..4e646784 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,157 @@ +{ + "images": [ + { + "idiom": "iphone", + "size": "29x29", + "scale": "1x" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "57x57", + "scale": "1x" + }, + { + "idiom": "iphone", + "size": "57x57", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "3x" + }, + { + "idiom": "ipad", + "size": "29x29", + "scale": "1x" + }, + { + "idiom": "ipad", + "size": "29x29", + "scale": "2x" + }, + { + "idiom": "ipad", + "size": "40x40", + "scale": "1x" + }, + { + "idiom": "ipad", + "size": "40x40", + "scale": "2x" + }, + { + "idiom": "ipad", + "size": "50x50", + "scale": "1x" + }, + { + "idiom": "ipad", + "size": "50x50", + "scale": "2x" + }, + { + "idiom": "ipad", + "size": "72x72", + "scale": "1x" + }, + { + "idiom": "ipad", + "size": "72x72", + "scale": "2x" + }, + { + "idiom": "ipad", + "size": "76x76", + "scale": "1x" + }, + { + "idiom": "ipad", + "size": "76x76", + "scale": "2x" + }, + { + "size": "24x24", + "idiom": "watch", + "scale": "2x", + "role": "notificationCenter", + "subtype": "38mm" + }, + { + "size": "27.5x27.5", + "idiom": "watch", + "scale": "2x", + "role": "notificationCenter", + "subtype": "42mm" + }, + { + "size": "29x29", + "idiom": "watch", + "role": "companionSettings", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "watch", + "role": "companionSettings", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "watch", + "scale": "2x", + "role": "appLauncher", + "subtype": "38mm" + }, + { + "size": "44x44", + "idiom": "watch", + "scale": "2x", + "role": "longLook", + "subtype": "42mm" + }, + { + "size": "86x86", + "idiom": "watch", + "scale": "2x", + "role": "quickLook", + "subtype": "38mm" + }, + { + "size": "98x98", + "idiom": "watch", + "scale": "2x", + "role": "quickLook", + "subtype": "42mm" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/Contents.json b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4caf392f --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Entitlements.plist b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Entitlements.plist new file mode 100644 index 00000000..9ae59937 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Facebook.YogaKit.iOS.Sample.csproj b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Facebook.YogaKit.iOS.Sample.csproj new file mode 100644 index 00000000..d94eb930 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Facebook.YogaKit.iOS.Sample.csproj @@ -0,0 +1,128 @@ + + + + Debug + iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Facebook.YogaKit.iOS.Sample + Facebook.YogaKit.iOS.Sample + Resources + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG;ENABLE_TEST_CLOUD;__IOS__ + prompt + 4 + iPhone Developer + true + true + true + true + true + 56768 + None + x86_64 + HttpClientHandler + Default + false + + + pdbonly + true + bin\iPhone\Release + + prompt + 4 + iPhone Developer + true + true + true + Entitlements.plist + SdkOnly + ARMv7, ARM64 + HttpClientHandler + Default + + + + true + bin\iPhoneSimulator\Release + + prompt + 4 + iPhone Developer + true + true + None + x86_64 + HttpClientHandler + Default + + + true + full + false + bin\iPhone\Debug + DEBUG;ENABLE_TEST_CLOUD; + prompt + 4 + iPhone Developer + true + true + true + true + true + true + true + Entitlements.plist + SdkOnly + ARMv7, ARM64 + HttpClientHandler + Default + + + + + + + + + + + + + + + + + + + + + + + + + + + + ViewController.cs + + + + + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0} + Facebook.YogaKit.iOS + + + {128FB32A-C4A1-4363-BF06-0A36E700B7FA} + Facebook.Yoga.iOS + + + + \ No newline at end of file diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Info.plist b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Info.plist new file mode 100644 index 00000000..0c6a791e --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleName + Facebook.YogaKit.iOS.Sample + CFBundleIdentifier + com.xamarin.facebook-yogakit-ios-sample + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.2 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/LaunchScreen.storyboard b/csharp/iOS/Facebook.YogaKit.iOS.Sample/LaunchScreen.storyboard new file mode 100644 index 00000000..5d2e905a --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.cs b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.cs new file mode 100644 index 00000000..c0e18ddf --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.cs @@ -0,0 +1,12 @@ +using UIKit; + +namespace Facebook.YogaKit.iOS.Sample +{ + public class Application + { + static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.storyboard b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.storyboard new file mode 100644 index 00000000..a43ad72c --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/Main.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.cs b/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.cs new file mode 100644 index 00000000..d4486a54 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.cs @@ -0,0 +1,54 @@ +using System; +using CoreGraphics; +using Facebook.Yoga; +using UIKit; + +namespace Facebook.YogaKit.iOS.Sample +{ + public partial class ViewController : UIViewController + { + protected ViewController(IntPtr handle) : base(handle) + { + // Note: this .ctor should not contain any initialization logic. + } + + public override void ViewDidLoad() + { + base.ViewDidLoad(); + CreateViewHierarchy(View, View.Bounds.Size.Width, View.Bounds.Size.Height); + } + + static void CreateViewHierarchy(UIView root, nfloat width, nfloat height) + { + root.BackgroundColor = UIColor.Red; + root.Yoga().IsEnabled = true; + + root.Yoga().Width = (float)width; + root.Yoga().Height = (float)height; + root.Yoga().AlignItems = YogaAlign.Center; + root.Yoga().JustifyContent = YogaJustify.Center; + + var child1 = new UIView { BackgroundColor = UIColor.Blue }; + child1.Yoga().IsEnabled = true; + child1.Yoga().Width = 100; + child1.Yoga().Height = 100; + + var child2 = new UIView + { + BackgroundColor = UIColor.Green, + Frame = new CGRect { Size = new CGSize(200, 100) } + }; + + var child3 = new UIView + { + BackgroundColor = UIColor.Yellow, + Frame = new CGRect { Size = new CGSize(100, 100) } + }; + + child2.AddSubview(child3); + root.AddSubview(child1); + root.AddSubview(child2); + root.Yoga().ApplyLayout(); + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.designer.cs b/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.designer.cs new file mode 100644 index 00000000..810950d0 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Sample/ViewController.designer.cs @@ -0,0 +1,17 @@ +// +// This file has been generated automatically by MonoDevelop to store outlets and +// actions made in the Xcode designer. If it is removed, they will be lost. +// Manual changes to this file may not be handled correctly. +// +using Foundation; + +namespace Facebook.YogaKit.iOS.Sample +{ + [Register("ViewController")] + partial class ViewController + { + void ReleaseDesignerOutlets() + { + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/Entitlements.plist b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Entitlements.plist new file mode 100644 index 00000000..9ae59937 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/Facebook.YogaKit.iOS.Tests.csproj b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Facebook.YogaKit.iOS.Tests.csproj new file mode 100644 index 00000000..626f3488 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Facebook.YogaKit.iOS.Tests.csproj @@ -0,0 +1,118 @@ + + + + Debug + iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Facebook.YogaKit.iOS.Tests + Facebook.YogaKit.iOS.Tests + Resources + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG; + prompt + 4 + iPhone Developer + true + true + true + true + true + 17481 + None + x86_64 + HttpClientHandler + Default + false + + + pdbonly + true + bin\iPhone\Release + + prompt + 4 + iPhone Developer + true + true + true + Entitlements.plist + SdkOnly + ARMv7, ARM64 + HttpClientHandler + Default + + + pdbonly + true + bin\iPhoneSimulator\Release + + prompt + 4 + iPhone Developer + true + true + None + x86_64 + HttpClientHandler + Default + + + true + full + false + bin\iPhone\Debug + DEBUG; + prompt + 4 + iPhone Developer + true + true + true + true + true + true + true + Entitlements.plist + SdkOnly + ARMv7, ARM64 + HttpClientHandler + Default + + + + + + + + + + + + + + + + + + + + + + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0} + Facebook.YogaKit.iOS + + + {128FB32A-C4A1-4363-BF06-0A36E700B7FA} + Facebook.Yoga.iOS + + + + + \ No newline at end of file diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/Info.plist b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Info.plist new file mode 100644 index 00000000..cf104fe2 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + Facebook.YogaKit.iOS.Tests + CFBundleIdentifier + com.xamarin.facebook-yogakit-ios-tests + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.2 + UIDeviceFamily + + 1 + 2 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UILaunchStoryboardName + LaunchScreen + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/LaunchScreen.storyboard b/csharp/iOS/Facebook.YogaKit.iOS.Tests/LaunchScreen.storyboard new file mode 100644 index 00000000..5d2e905a --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/Main.cs b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Main.cs new file mode 100644 index 00000000..0da0a414 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/Main.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +using Foundation; +using UIKit; + +namespace Facebook.YogaKit.iOS.Tests +{ + public class Application + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "UnitTestAppDelegate" + // you can specify it here. + UIApplication.Main(args, null, "UnitTestAppDelegate"); + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS.Tests/UnitTestAppDelegate.cs b/csharp/iOS/Facebook.YogaKit.iOS.Tests/UnitTestAppDelegate.cs new file mode 100644 index 00000000..186d2380 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.Tests/UnitTestAppDelegate.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +using Foundation; +using UIKit; +using MonoTouch.NUnit.UI; + +namespace Facebook.YogaKit.iOS.Tests +{ + // The UIApplicationDelegate for the application. This class is responsible for launching the + // User Interface of the application, as well as listening (and optionally responding) to + // application events from iOS. + [Register("UnitTestAppDelegate")] + public partial class UnitTestAppDelegate : UIApplicationDelegate + { + // class-level declarations + UIWindow window; + TouchRunner runner; + + // + // This method is invoked when the application has loaded and is ready to run. In this + // method you should instantiate the window, load the UI into it and then make the window + // visible. + // + // You have 17 seconds to return from this method, or iOS will terminate your application. + // + public override bool FinishedLaunching(UIApplication app, NSDictionary options) + { + // create a new window instance based on the screen size + window = new UIWindow(UIScreen.MainScreen.Bounds); + runner = new TouchRunner(window); + + // register every tests included in the main application/assembly + runner.Add(System.Reflection.Assembly.GetExecutingAssembly()); + + window.RootViewController = new UINavigationController(runner.GetViewController()); + + // make the window visible + window.MakeKeyAndVisible(); + + return true; + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS.sln b/csharp/iOS/Facebook.YogaKit.iOS.sln new file mode 100644 index 00000000..925ce4ba --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9FB621AA-66DD-46F1-8B10-94F508272A8D}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Facebook.YogaKit.Shared", "..\Facebook.YogaKit\Facebook.YogaKit.Shared.shproj", "{A24B3BA6-3143-4FFF-B8B8-1EDF166F5F4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.YogaKit.iOS", "Facebook.YogaKit.iOS\Facebook.YogaKit.iOS.csproj", "{0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.YogaKit.iOS.Sample", "Facebook.YogaKit.iOS.Sample\Facebook.YogaKit.iOS.Sample.csproj", "{6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Facebook.Yoga.Shared", "..\Facebook.Yoga\Facebook.Yoga.Shared.shproj", "{91C42D32-291D-4B72-90B4-551663D60B8B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.Yoga.iOS", "Facebook.Yoga.iOS\Facebook.Yoga.iOS.csproj", "{128FB32A-C4A1-4363-BF06-0A36E700B7FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.YogaKit.iOS.Tests", "Facebook.YogaKit.iOS.Tests\Facebook.YogaKit.iOS.Tests.csproj", "{238DB3A2-1182-4775-92BC-F62C885725D8}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Facebook.YogaKit.Shared.Tests", "..\tests\Facebook.YogaKit.Shared.Tests\Facebook.YogaKit.Shared.Tests.shproj", "{63AB08F4-4F7C-42B7-A20F-D84204D0D3CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|Any CPU.Build.0 = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|iPhone.ActiveCfg = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|iPhone.Build.0 = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0}.Debug|iPhone.Build.0 = Debug|Any CPU + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|Any CPU.ActiveCfg = Release|iPhone + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|Any CPU.Build.0 = Release|iPhone + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|iPhone.ActiveCfg = Release|iPhone + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|iPhone.Build.0 = Release|iPhone + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|iPhone.ActiveCfg = Debug|iPhone + {6A094B74-FA9A-4E49-A8E1-F450A04E3E5B}.Debug|iPhone.Build.0 = Debug|iPhone + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|Any CPU.Build.0 = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|iPhone.ActiveCfg = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|iPhone.Build.0 = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {128FB32A-C4A1-4363-BF06-0A36E700B7FA}.Debug|iPhone.Build.0 = Debug|Any CPU + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|Any CPU.ActiveCfg = Release|iPhone + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|Any CPU.Build.0 = Release|iPhone + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|iPhone.ActiveCfg = Release|iPhone + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|iPhone.Build.0 = Release|iPhone + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|iPhone.ActiveCfg = Debug|iPhone + {238DB3A2-1182-4775-92BC-F62C885725D8}.Debug|iPhone.Build.0 = Debug|iPhone + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A24B3BA6-3143-4FFF-B8B8-1EDF166F5F4A} = {9FB621AA-66DD-46F1-8B10-94F508272A8D} + {91C42D32-291D-4B72-90B4-551663D60B8B} = {9FB621AA-66DD-46F1-8B10-94F508272A8D} + {63AB08F4-4F7C-42B7-A20F-D84204D0D3CE} = {9FB621AA-66DD-46F1-8B10-94F508272A8D} + EndGlobalSection +EndGlobal diff --git a/csharp/iOS/Facebook.YogaKit.iOS/Facebook.YogaKit.iOS.csproj b/csharp/iOS/Facebook.YogaKit.iOS/Facebook.YogaKit.iOS.csproj new file mode 100644 index 00000000..4ecb3283 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS/Facebook.YogaKit.iOS.csproj @@ -0,0 +1,69 @@ + + + + Debug + AnyCPU + {0C38AA9D-3178-4B43-9C3B-3C97A90FB1B0} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Facebook.YogaKit.iOS + Facebook.YogaKit.iOS + Resources + + + true + full + false + bin\Debug + DEBUG;__IOS__ + prompt + 4 + iPhone Developer + true + true + true + true + true + 53781 + false + SdkOnly + HttpClientHandler + Default + + + pdbonly + true + bin\Release + + prompt + 4 + iPhone Developer + true + true + SdkOnly + HttpClientHandler + Default + + + + + + + + + + + + + + + + + + {128FB32A-C4A1-4363-BF06-0A36E700B7FA} + Facebook.Yoga.iOS + + + + + \ No newline at end of file diff --git a/csharp/iOS/Facebook.YogaKit.iOS/Properties/AssemblyInfo.cs b/csharp/iOS/Facebook.YogaKit.iOS/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..944ab239 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("Facebook.YogaKit.iOS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("rmarinho")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/csharp/iOS/Facebook.YogaKit.iOS/YogaKit.cs b/csharp/iOS/Facebook.YogaKit.iOS/YogaKit.cs new file mode 100644 index 00000000..e957ed91 --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS/YogaKit.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using Foundation; +using UIKit; + +namespace Facebook.YogaKit +{ + public static partial class YogaKit + { + static NSString YogaNodeKey = new NSString("YogaNode"); + + public static IYogaLayout Yoga(this UIView view) + { + return YogaLayoutNative(view); + } + + static IYogaLayout YogaLayoutNative(UIView view) + { + var yoga = ObjCRuntime.Runtime.GetNSObject(objc_getAssociatedObject(view.Handle, YogaNodeKey.Handle)) as YogaLayout; + if (yoga == null) + { + yoga = new YogaLayout(view); + objc_setAssociatedObject(view.Handle, YogaNodeKey.Handle, yoga.Handle, AssociationPolicy.RETAIN_NONATOMIC); + } + return yoga; + } + + [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, + } + } +} diff --git a/csharp/iOS/Facebook.YogaKit.iOS/YogaLayout.cs b/csharp/iOS/Facebook.YogaKit.iOS/YogaLayout.cs new file mode 100644 index 00000000..6aa052bf --- /dev/null +++ b/csharp/iOS/Facebook.YogaKit.iOS/YogaLayout.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using CoreGraphics; +using Facebook.Yoga; +using Foundation; +using UIKit; + +namespace Facebook.YogaKit +{ + public partial class YogaLayout : NSObject + { + static IEnumerable GetChildren(UIView view) => view.Subviews; + + static void MeasureNativeView(UIView view, float constrainedWidth, float constrainedHeight, out float sizeThatFitsWidth, out float sizeThatFitsHeight) + { + var sizeThatFits = view.SizeThatFits(new CGSize(constrainedWidth, constrainedHeight)); + sizeThatFitsWidth = (float)sizeThatFits.Width; + sizeThatFitsHeight = (float)sizeThatFits.Height; + } + + static void GetWidthHeightOfNativeView(UIView view, out float width, out float height) + { + width = (float)view.Bounds.Width; + height = (float)view.Bounds.Height; + } + + static float NativePixelScale => (float)UIScreen.MainScreen.Scale; + + + static void ApplyLayoutToNativeView(UIView view, YogaNode node) + { + var topLeft = new CGPoint(node.LayoutX, node.LayoutY); + var bottomRight = new CGPoint(topLeft.X + node.LayoutWidth, topLeft.Y + node.LayoutHeight); + view.Frame = new CGRect(RoundPixelValue((float)topLeft.X), RoundPixelValue((float)topLeft.Y), RoundPixelValue((float)bottomRight.X) - RoundPixelValue((float)topLeft.X), RoundPixelValue((float)bottomRight.Y) - RoundPixelValue((float)topLeft.Y)); + } + + bool _disposed; + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (YogaKit.Bridges.ContainsKey(_node)) + { + YogaKit.Bridges.Remove(_node); + } + _node = null; + _viewRef = null; + } + base.Dispose(disposing); + } + } +} diff --git a/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.projitems b/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.projitems new file mode 100644 index 00000000..04cc2739 --- /dev/null +++ b/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {63AB08F4-4F7C-42B7-A20F-D84204D0D3CE} + + + Facebook.YogaKit.Shared.Tests + + + + + \ No newline at end of file diff --git a/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.shproj b/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.shproj new file mode 100644 index 00000000..4039eea4 --- /dev/null +++ b/csharp/tests/Facebook.YogaKit.Shared.Tests/Facebook.YogaKit.Shared.Tests.shproj @@ -0,0 +1,11 @@ + + + + {63AB08F4-4F7C-42B7-A20F-D84204D0D3CE} + + + + + + + \ No newline at end of file diff --git a/csharp/tests/Facebook.YogaKit.Shared.Tests/YogaKitTest.cs b/csharp/tests/Facebook.YogaKit.Shared.Tests/YogaKitTest.cs new file mode 100644 index 00000000..23b6ae1e --- /dev/null +++ b/csharp/tests/Facebook.YogaKit.Shared.Tests/YogaKitTest.cs @@ -0,0 +1,369 @@ +using System.Drawing; +using Facebook.Yoga; +using NUnit.Framework; +using System; +#if __IOS__ +using NativeView = UIKit.UIView; +using CoreGraphics; +#endif + +namespace Facebook.YogaKit +{ + [TestFixture] + public class YogaKitTest + { + [Test] + public void TestUsesYoga() + { + var view = new NativeView(); + Assert.False(view.Yoga().IsEnabled); + view.Yoga().IsEnabled = true; + Assert.True(view.Yoga().IsEnabled); + view.Yoga().IsEnabled = false; + Assert.False(view.Yoga().IsEnabled); + } + + [Test] + public void TestSizeThatFitsAsserts() + { + var view = new NativeView(); + Assert.Ignore("Not implemented yet"); + // dispatch_sync(dispatch_queue_create("com.facebook.Yoga.testing", DISPATCH_QUEUE_SERIAL), ^(void){ + // XCTAssertThrows(view.yoga.intrinsicSize); + // }); + } + + [Test] + public void TestSizeThatFitsSmoke() + { + var container = new NativeView(); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + container.Yoga().AlignItems = YogaAlign.FlexStart; + + var longTextLabel = new UIKit.UILabel(); + longTextLabel.Text = @"This is a very very very very very very very very long piece of text."; + longTextLabel.LineBreakMode = UIKit.UILineBreakMode.TailTruncation; + longTextLabel.Lines = 1; + longTextLabel.Yoga().IsEnabled = true; + longTextLabel.Yoga().FlexShrink = 1; + container.AddSubview(longTextLabel); + + var textBadgeView = new NativeView(); + textBadgeView.Yoga().IsEnabled = true; + textBadgeView.Yoga().MarginLeft = 3.0f; + textBadgeView.Yoga().Width = 10; + textBadgeView.Yoga().Height = 10; + container.AddSubview(textBadgeView); + + var containerSize = container.Yoga().IntrinsicSize; + Assert.True(new SizeF(514, 21) == containerSize, $"Size is actually {containerSize})"); + } + + [Test] + public void TestThatMarkingLeafsAsDirtyWillTriggerASizeRecalculation() + { + var container = new NativeView(new CGRect(0, 0, 500, 500)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + container.Yoga().AlignItems = YogaAlign.FlexStart; + + var label = new UIKit.UILabel(); + label.Text = @"This is a short text."; + label.Lines = 1; + label.Yoga().IsEnabled = true; + container.AddSubview(label); + + container.Yoga().ApplyLayout(); + Assert.True(new SizeF(146, 21) == label.Bounds.Size, $"Size is actually {label.Bounds.Size})"); + + label.Text = @"This is a slightly longer text."; + label.Yoga().MarkDirty(); + + container.Yoga().ApplyLayout(); + Assert.True(new SizeF(213, 21) == label.Bounds.Size, $"Size is actually {label.Bounds.Size})"); + } + + [Test] + public void TestFrameAndOriginPlacement() + { + var containerSize = new SizeF(320, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + + for (int i = 0; i < 3; i++) + { + var subview = new NativeView(); + subview.Yoga().IsEnabled = true; + subview.Yoga().FlexGrow = 1; + container.AddSubview(subview); + } + container.Yoga().ApplyLayout(); + + Assert.False(CGRect.Intersect(container.Subviews[0].Frame, container.Subviews[1].Frame) == CGRect.Empty); + Assert.False(CGRect.Intersect(container.Subviews[1].Frame, container.Subviews[2].Frame) == CGRect.Empty); + Assert.False(CGRect.Intersect(container.Subviews[0].Frame, container.Subviews[2].Frame) == CGRect.Empty); + + float totalWidth = 0; + foreach (var view in container.Subviews) + totalWidth += (float)view.Bounds.Size.Width; + + Assert.AreEqual(containerSize.Width, totalWidth, $"The container's width is {containerSize.Width}, the subviews take up {totalWidth}"); + } + + + [Test] + public void TestThatLayoutIsCorrectWhenWeSwapViewOrder() + { + var containerSize = new SizeF(300, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + subview1.Yoga().FlexGrow = 1; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + subview2.Yoga().FlexGrow = 1; + container.AddSubview(subview2); + + var subview3 = new NativeView(); + subview3.Yoga().IsEnabled = true; + subview3.Yoga().FlexGrow = 1; + container.AddSubview(subview3); + + container.Yoga().ApplyLayout(); + + Assert.True(subview1.Frame == new CGRect(0, 0, 100, 50)); + Assert.True(subview2.Frame == new CGRect(100, 0, 100, 50), $"It's actually {subview2.Frame}"); + Assert.True(subview3.Frame == new CGRect(200, 0, 100, 50)); + + container.ExchangeSubview(2, 0); + subview2.Yoga().IsIncludeInLayout = false; + container.Yoga().ApplyLayout(); + + Assert.True(subview3.Frame == new CGRect(0, 0, 150, 50)); + Assert.True(subview1.Frame == new CGRect(150, 0, 150, 50)); + //// this frame shouldn't have been modified since last time. + Assert.True(subview2.Frame == new CGRect(100, 0, 100, 50)); + } + + [Test] + public void TestThatWeRespectIncludeInLayoutFlag() + { + var containerSize = new SizeF(300, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + subview1.Yoga().FlexGrow = 1; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + subview2.Yoga().FlexGrow = 1; + container.AddSubview(subview2); + + var subview3 = new NativeView(); + subview3.Yoga().IsEnabled = true; + subview3.Yoga().FlexGrow = 1; + container.AddSubview(subview3); + + container.Yoga().ApplyLayout(); + + foreach (var view in container.Subviews) + { + Assert.True(new CGSize(100, 50) == view.Bounds.Size, $"Actual size is {view.Bounds.Size}"); + } + + subview3.Yoga().IsIncludeInLayout = false; + container.Yoga().ApplyLayout(); + Assert.True(subview1.Frame.Size == new CGSize(150, 50), $"Actual size is {subview1.Frame.Size}"); + Assert.True(subview2.Frame.Size == new CGSize(150, 50), $"Actual size is {subview2.Frame.Size}"); + //// We don't set the frame to zero, so, it should be set to what it was previously at. + Assert.True(subview3.Frame.Size == new CGSize(100, 50), $"Actual size is {subview3.Frame.Size}"); + } + + [Test] + public void TestThatNumberOfChildrenIsCorrectWhenWeIgnoreSubviews() + { + var container = new NativeView(); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + subview1.Yoga().IsIncludeInLayout = false; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + subview2.Yoga().IsIncludeInLayout = false; + container.AddSubview(subview2); + + var subview3 = new NativeView(); + subview3.Yoga().IsEnabled = true; + subview3.Yoga().IsIncludeInLayout = true; + container.AddSubview(subview3); + + container.Yoga().ApplyLayout(); + Assert.AreEqual(1, container.Yoga().NumberOfChildren); + + subview2.Yoga().IsIncludeInLayout = true; + container.Yoga().ApplyLayout(); + Assert.AreEqual(2, container.Yoga().NumberOfChildren); + } + + [Test] + public void TestThatViewNotIncludedInFirstLayoutPassAreIncludedInSecond() + { + var containerSize = new SizeF(300, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Row; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + subview1.Yoga().FlexGrow = 1; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + subview2.Yoga().FlexGrow = 1; + container.AddSubview(subview2); + + var subview3 = new NativeView(); + subview3.Yoga().IsEnabled = true; + subview3.Yoga().FlexGrow = 1; + subview3.Yoga().IsIncludeInLayout = false; + container.AddSubview(subview3); + + container.Yoga().ApplyLayout(); + + Assert.True(subview1.Frame.Size == new CGSize(150, 50), $"Actual size is {subview1.Frame.Size}"); + Assert.True(subview2.Frame.Size == new CGSize(150, 50), $"Actual size is {subview2.Frame.Size}"); + Assert.True(subview3.Frame.Size == CGSize.Empty, $"Actual size is {subview3.Frame.Size}"); + + subview3.Yoga().IsIncludeInLayout = true; + container.Yoga().ApplyLayout(); + + foreach (var view in container.Subviews) + { + Assert.True(new CGSize(100, 50) == view.Bounds.Size, $"Actual size is {view.Bounds.Size}"); + } + } + + [Test] + public void TestYogaIsLeafFlag() + { + var view = new NativeView(); + Assert.True(view.Yoga().IsLeaf); + + for (int i = 0; i < 10; i++) + { + var subView = new NativeView(); + view.AddSubview(subView); + } + Assert.True(view.Yoga().IsLeaf); + view.Yoga().IsEnabled = true; + view.Yoga().Width = 50.0f; + Assert.True(view.Yoga().IsLeaf); + + var subView1 = view.Subviews[0]; + subView1.Yoga().IsEnabled = true; + subView1.Yoga().Width = 50.0f; + Assert.False(view.Yoga().IsLeaf); + } + + [Test] + public void TestThatWeCorrectlyAttachNestedViews() + { + var containerSize = new SizeF(300, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + container.Yoga().FlexDirection = YogaFlexDirection.Column; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + subview1.Yoga().Width = 100; + subview1.Yoga().FlexGrow = 1; + subview1.Yoga().FlexDirection = YogaFlexDirection.Column; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + subview2.Yoga().Width = 150; + subview2.Yoga().FlexGrow = 1; + subview2.Yoga().FlexDirection = YogaFlexDirection.Column; + container.AddSubview(subview2); + + foreach (var view in new NativeView[] { subview1, subview2 }) + { + var someView = new NativeView(); + someView.Yoga().IsEnabled = true; + someView.Yoga().FlexGrow = 1; + view.AddSubview(someView); + } + container.Yoga().ApplyLayout(); + + // Add the same amount of new views, reapply layout. + foreach (var view in new NativeView[] { subview1, subview2 }) + { + var someView = new NativeView(); + someView.Yoga().IsEnabled = true; + someView.Yoga().FlexGrow = 1; + view.AddSubview(someView); + } + container.Yoga().ApplyLayout(); + + Assert.True(new CGSize(100, 25) == subview1.Bounds.Size, $"Actual size is {subview1.Bounds.Size}"); + foreach (var subview in subview1.Subviews) + { + var subviewSize = subview.Bounds.Size; + Assert.False(subviewSize.IsEmpty); + Assert.False(nfloat.IsNaN(subviewSize.Height)); + Assert.False(nfloat.IsNaN(subviewSize.Width)); + } + + Assert.True(new CGSize(150, 25) == subview2.Bounds.Size, $"Actual size is {subview2.Bounds.Size}"); + foreach (var subview in subview2.Subviews) + { + var subviewSize = subview.Bounds.Size; + Assert.False(subviewSize.IsEmpty); + Assert.False(nfloat.IsNaN(subviewSize.Height)); + Assert.False(nfloat.IsNaN(subviewSize.Width)); + } + } + + [Test] + public void TestThatANonLeafNodeCanBecomeALeafNode() + { + var containerSize = new SizeF(300, 50); + + var container = new NativeView(new CGRect(0, 0, containerSize.Width, containerSize.Height)); + container.Yoga().IsEnabled = true; + + var subview1 = new NativeView(); + subview1.Yoga().IsEnabled = true; + container.AddSubview(subview1); + + var subview2 = new NativeView(); + subview2.Yoga().IsEnabled = true; + container.AddSubview(subview2); + container.Yoga().ApplyLayout(); + subview2.RemoveFromSuperview(); + container.Yoga().ApplyLayout(); + } + } +}