use crate::error::Result;
use objc::runtime::Object;
#[cfg(not(test))]
use objc::{msg_send, sel, sel_impl};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LayoutAttribute {
Left = 1,
Right = 2,
Top = 3,
Bottom = 4,
Leading = 5,
Trailing = 6,
Width = 7,
Height = 8,
CenterX = 9,
CenterY = 10,
Baseline = 11,
FirstBaseline = 12,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutRelation {
LessThanOrEqual = -1,
Equal = 0,
GreaterThanOrEqual = 1,
}
#[derive(Debug, Clone, Copy)]
pub struct LayoutPriority(pub f32);
impl LayoutPriority {
pub const REQUIRED: LayoutPriority = LayoutPriority(1000.0);
pub const HIGH: LayoutPriority = LayoutPriority(750.0);
pub const MEDIUM: LayoutPriority = LayoutPriority(500.0);
pub const LOW: LayoutPriority = LayoutPriority(250.0);
pub const FITTING_SIZE_COMPRESSION: LayoutPriority = LayoutPriority(750.0);
}
#[derive(Debug, Clone)]
#[must_use = "ConstraintDescriptor must be added to a View with .constraint()"]
pub struct ConstraintDescriptor {
pub first_attribute: LayoutAttribute,
pub relation: LayoutRelation,
pub second_attribute: Option<LayoutAttribute>,
pub multiplier: f64,
pub constant: f64,
pub priority: LayoutPriority,
}
impl Default for ConstraintDescriptor {
fn default() -> Self {
Self {
first_attribute: LayoutAttribute::Width,
relation: LayoutRelation::Equal,
second_attribute: None,
multiplier: 1.0,
constant: 0.0,
priority: LayoutPriority::REQUIRED,
}
}
}
impl ConstraintDescriptor {
pub fn new(attr: LayoutAttribute) -> Self {
Self {
first_attribute: attr,
relation: LayoutRelation::Equal,
second_attribute: None,
multiplier: 1.0,
constant: 0.0,
priority: LayoutPriority::REQUIRED,
}
}
pub fn equal_to(mut self, attr: LayoutAttribute) -> Self {
self.second_attribute = Some(attr);
self.relation = LayoutRelation::Equal;
self
}
pub fn constant(mut self, c: f64) -> Self {
self.constant = c;
self
}
pub fn priority(mut self, p: LayoutPriority) -> Self {
self.priority = p;
self
}
}
pub mod constraints {
use super::*;
pub fn fill_superview() -> Vec<ConstraintDescriptor> {
vec![
ConstraintDescriptor::new(LayoutAttribute::Top).equal_to(LayoutAttribute::Top),
ConstraintDescriptor::new(LayoutAttribute::Bottom).equal_to(LayoutAttribute::Bottom),
ConstraintDescriptor::new(LayoutAttribute::Leading).equal_to(LayoutAttribute::Leading),
ConstraintDescriptor::new(LayoutAttribute::Trailing)
.equal_to(LayoutAttribute::Trailing),
]
}
pub fn center_in_superview() -> Vec<ConstraintDescriptor> {
vec![
ConstraintDescriptor::new(LayoutAttribute::CenterX).equal_to(LayoutAttribute::CenterX),
ConstraintDescriptor::new(LayoutAttribute::CenterY).equal_to(LayoutAttribute::CenterY),
]
}
pub fn width(w: f64) -> ConstraintDescriptor {
ConstraintDescriptor::new(LayoutAttribute::Width).constant(w)
}
pub fn height(h: f64) -> ConstraintDescriptor {
ConstraintDescriptor::new(LayoutAttribute::Height).constant(h)
}
pub fn aspect_ratio(ratio: f64) -> ConstraintDescriptor {
let mut c = ConstraintDescriptor::new(LayoutAttribute::Width);
c.second_attribute = Some(LayoutAttribute::Height);
c.multiplier = ratio;
c
}
}
#[cfg(not(test))]
pub unsafe fn apply_constraints(
view: *mut Object,
superview: *mut Object,
descriptors: &[ConstraintDescriptor],
) -> Result<()> {
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: false];
let constraint_class = objc::class!(NSLayoutConstraint);
for desc in descriptors {
let constraint: *mut Object = if let Some(second_attr) = desc.second_attribute {
msg_send![
constraint_class,
constraintWithItem: view
attribute: desc.first_attribute as i64
relatedBy: desc.relation as i64
toItem: superview
attribute: second_attr as i64
multiplier: desc.multiplier
constant: desc.constant
]
} else {
msg_send![
constraint_class,
constraintWithItem: view
attribute: desc.first_attribute as i64
relatedBy: desc.relation as i64
toItem: std::ptr::null_mut::<Object>()
attribute: 0_i64
multiplier: 1.0
constant: desc.constant
]
};
let _: () = msg_send![constraint, setPriority: desc.priority.0];
let _: () = msg_send![superview, addConstraint: constraint];
}
Ok(())
}
#[cfg(not(test))]
pub unsafe fn set_uses_auto_layout(view: *mut Object, uses: bool) {
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: !uses];
}
#[cfg(not(test))]
pub unsafe fn intrinsic_content_size(view: *mut Object) -> (f64, f64) {
let size: cocoa::foundation::NSSize = msg_send![view, intrinsicContentSize];
(size.width, size.height)
}
#[cfg(test)]
pub unsafe fn apply_constraints(
_view: *mut Object,
_superview: *mut Object,
_descriptors: &[ConstraintDescriptor],
) -> Result<()> {
Ok(())
}
#[cfg(test)]
pub unsafe fn set_uses_auto_layout(_view: *mut Object, _uses: bool) {}
#[cfg(test)]
pub unsafe fn intrinsic_content_size(_view: *mut Object) -> (f64, f64) {
(0.0, 0.0)
}
#[cfg(test)]
mod tests {
use super::constraints;
use super::{ConstraintDescriptor, LayoutAttribute, LayoutPriority, LayoutRelation};
#[test]
fn constraint_descriptor_new_and_chain() {
let d = ConstraintDescriptor::new(LayoutAttribute::Width)
.equal_to(LayoutAttribute::Height)
.constant(12.5)
.priority(LayoutPriority::HIGH);
assert_eq!(d.first_attribute, LayoutAttribute::Width);
assert_eq!(d.second_attribute, Some(LayoutAttribute::Height));
assert_eq!(d.constant, 12.5);
assert_eq!(d.relation, LayoutRelation::Equal);
assert_eq!(d.priority.0, 750.0);
}
#[test]
fn constraint_default() {
let d = ConstraintDescriptor::default();
assert_eq!(d.first_attribute, LayoutAttribute::Width);
assert_eq!(d.multiplier, 1.0);
}
#[test]
fn fill_superview_four_constraints() {
let v = constraints::fill_superview();
assert_eq!(v.len(), 4);
}
#[test]
fn center_in_superview_two() {
let v = constraints::center_in_superview();
assert_eq!(v.len(), 2);
}
#[test]
fn width_height_aspect_helpers() {
let w = constraints::width(200.0);
assert_eq!(w.first_attribute, LayoutAttribute::Width);
assert_eq!(w.constant, 200.0);
let h = constraints::height(44.0);
assert_eq!(h.first_attribute, LayoutAttribute::Height);
assert_eq!(h.constant, 44.0);
let a = constraints::aspect_ratio(16.0 / 9.0);
assert_eq!(a.first_attribute, LayoutAttribute::Width);
assert_eq!(a.second_attribute, Some(LayoutAttribute::Height));
assert!((a.multiplier - 16.0 / 9.0).abs() < f64::EPSILON);
}
#[test]
fn layout_attribute_relation_discriminant() {
assert_eq!(LayoutAttribute::Left as i32, 1);
assert_eq!(LayoutRelation::Equal as i32, 0);
}
#[test]
fn apply_constraints_mock_ok() {
let descs = constraints::width(100.0);
let r = unsafe { super::apply_constraints(std::ptr::null_mut(), std::ptr::null_mut(), &[descs]) };
assert!(r.is_ok());
}
}