Skip to content

Instantly share code, notes, and snippets.

@7sharp9
Forked from praeclarum/EasyLayout.fs
Last active August 29, 2015 14:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 7sharp9/d807ee3178856f0e0f09 to your computer and use it in GitHub Desktop.
Save 7sharp9/d807ee3178856f0e0f09 to your computer and use it in GitHub Desktop.
module EasyLayout
open System
open System.Drawing
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
open MonoTouch.Foundation
open MonoTouch.UIKit
module private Utilities =
let rec eval e =
match e with
| FieldGet (Some o, f) -> f.GetValue (eval o)
| FieldGet (None, f) -> f.GetValue (null)
| PropertyGet (None, p, i) -> p.GetValue (null, i |> Seq.map eval |> Seq.toArray)
| PropertyGet (Some o, p, i) -> p.GetValue (eval o, i |> Seq.map eval |> Seq.toArray)
| Value (x, _) -> x
| _ -> failwithf "Don't know how to eval %A" e
let toAttr m =
match m with
| "X" | "Left" -> NSLayoutAttribute.Left
| "Y" | "Top" -> NSLayoutAttribute.Top
| "Width" -> NSLayoutAttribute.Width
| "Height" -> NSLayoutAttribute.Height
| "Bottom" -> NSLayoutAttribute.Bottom
| "Right" -> NSLayoutAttribute.Right
| "CenterX" | "RectangleF.get_CenterX" -> NSLayoutAttribute.CenterX
| "CenterY" | "RectangleF.get_CenterY" -> NSLayoutAttribute.CenterY
| "Baseline" | "RectangleF.get_Baseline" -> NSLayoutAttribute.Baseline
| _ -> NSLayoutAttribute.NoAttribute
let (|ConstrainableProp|_|) (m:Reflection.MemberInfo) =
match toAttr m.Name with NSLayoutAttribute.NoAttribute -> None | others -> Some(others)
let (|FrameProp|_|) (pi:Reflection.PropertyInfo) = if pi.Name = "Frame" then Some(pi) else None
let (|GetFrameProp|_|) e =
match e with
| Let (_, PropertyGet (Some o, FrameProp(fn), _), PropertyGet (_, ConstrainableProp(pn), _)) -> Some (eval o :?> NSObject, pn)
| Call (_, ConstrainableProp(pn), [PropertyGet (Some o, FrameProp(fn), _)]) -> Some (eval o :?> NSObject, pn)
| _ -> None
let compileLeftSide = function
| GetFrameProp (x) -> x
| side -> failwithf "Left hand side of constraint is expected to be a UIView.Frame property. It was: %A" side
let (|NamedMethod|_|) name (m:Reflection.MethodInfo) = if m.Name = name then Some(m) else None
let (|Mul|_|) = function
| GetFrameProp (x, p) -> Some (x, p, 1.0f)
| Call (_, NamedMethod "op_Multiply" _, [l; GetFrameProp (x, p)]) -> Some (x, p, Convert.ToSingle (eval l))
| Call (_, NamedMethod "op_Multiply" _, [GetFrameProp (x, p); l]) -> Some (x, p, Convert.ToSingle (eval l))
| _ -> None
let compileRightSide side =
match side with
| Mul x -> (Some x, 0.0f)
| Call (_, NamedMethod "op_Addition" _, [Mul(x); c]) -> (Some x, Convert.ToSingle (eval c))
| Call (_, NamedMethod "op_Subtraction" _, [Mul(x); c]) -> (Some x, -Convert.ToSingle (eval c))
| Value (x, _) -> (None, Convert.ToSingle (x))
| FieldGet _ -> (None, Convert.ToSingle (eval side))
| _ -> failwithf "Unrecognized right hand side: %A." side
let compileConstraint left right rel =
let (firstObj, firstAttr) = compileLeftSide left
let (maybeObj, add) = compileRightSide right
match maybeObj with
| None -> NSLayoutConstraint.Create (firstObj, firstAttr, rel, null, NSLayoutAttribute.NoAttribute, 0.0f, add)
| Some (secObj, secAttr, mul) -> NSLayoutConstraint.Create (firstObj, firstAttr, rel, secObj, secAttr, mul, add)
let (|ToRel|_|) (m:System.Reflection.MethodInfo) =
match m.Name with
| "op_Equality" -> Some NSLayoutRelation.Equal
| "op_LessThanOrEqual" -> Some NSLayoutRelation.LessThanOrEqual
| "op_GreaterThanOrEqual" -> Some NSLayoutRelation.GreaterThanOrEqual
| _ -> None
let rec compileConstraints expr =
match expr with
| NewArray (_, es) -> es |> Seq.collect compileConstraints |> Seq.toList
| IfThenElse (i, t, e) -> compileConstraints i @ compileConstraints t @ compileConstraints e
| Call (_, ToRel(m), [l; r]) -> [compileConstraint l r m]
| Value _ -> []
| _ -> failwithf "Unable to recognize constraints in expression: %A" expr
type RectangleF with
member this.CenterX = this.X + this.Width / 2.0f
member this.CenterY = this.Y + this.Height / 2.0f
member this.Baseline = 0.0f
type UIView with
/// <summary>
/// <para>Constrains the layout of subviews according to equations and
/// inequalities specified in <paramref name="constraints"/>. Issue
/// multiple constraints per call using the &amp;&amp; operator.</para>
/// <code><@ button.Frame.Left &gt;= text.Frame.Right + 22 &amp;&amp;
/// button.Frame.Width = View.Frame.Width * 0.42f @></code>
/// </summary>
/// <param name="constraints">Constraint equations and inequalities.</param>
member this.ConstrainLayout (constraints) =
let cs = Utilities.compileConstraints constraints |> Seq.toArray
this.AddConstraints (cs)
for x in cs do
(x.FirstItem :?> UIView).TranslatesAutoresizingMaskIntoConstraints <- false
cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment