#import "modules/std.sx"; #import "modules/math"; Point :: struct { x, y: f32; zero :: () -> Point => .{ x = 0.0, y = 0.0 }; add :: (self: Point, b: Point) -> Point { Point.{ x = self.x + b.x, y = self.y + b.y }; } sub :: (self: Point, b: Point) -> Point { Point.{ x = self.x - b.x, y = self.y - b.y }; } scale :: (self: Point, s: f32) -> Point { Point.{ x = self.x * s, y = self.y * s }; } distance :: (self: Point, b: Point) -> f32 { dx := self.x - b.x; dy := self.y - b.y; sqrt(dx * dx + dy * dy); } } Size :: struct { width, height: f32; zero :: () -> Size => .{ width = 0.0, height = 0.0 }; contains :: (self: Size, point: Point) -> bool { point.x >= 0.0 and point.x <= self.width and point.y >= 0.0 and point.y <= self.height; } } Frame :: struct { origin: Point; size: Size; zero :: () -> Frame { Frame.{ origin = Point.zero(), size = Size.zero() }; } make :: (x: f32, y: f32, w: f32, h: f32) -> Frame { Frame.{ origin = Point.{ x = x, y = y }, size = Size.{ width = w, height = h } }; } max_x :: (self: Frame) -> f32 { self.origin.x + self.size.width; } max_y :: (self: Frame) -> f32 { self.origin.y + self.size.height; } mid_x :: (self: Frame) -> f32 { self.origin.x + self.size.width * 0.5; } mid_y :: (self: Frame) -> f32 { self.origin.y + self.size.height * 0.5; } contains :: (self: Frame, point: Point) -> bool { point.x >= self.origin.x and point.x <= self.max_x() and point.y >= self.origin.y and point.y <= self.max_y(); } intersection :: (self: Frame, other: Frame) -> Frame { x1 := max(self.origin.x, other.origin.x); y1 := max(self.origin.y, other.origin.y); x2 := min(self.max_x(), other.max_x()); y2 := min(self.max_y(), other.max_y()); if x2 <= x1 or y2 <= y1 then .zero() else .make(x1, y1, x2 - x1, y2 - y1); } inset :: (self: Frame, insets: EdgeInsets) -> Frame { Frame.make( self.origin.x + insets.left, self.origin.y + insets.top, self.size.width - insets.left - insets.right, self.size.height - insets.top - insets.bottom ); } expand :: (self: Frame, amount: f32) -> Frame { Frame.make( self.origin.x - amount, self.origin.y - amount, self.size.width + amount * 2.0, self.size.height + amount * 2.0 ); } } EdgeInsets :: struct { top, left, bottom, right: f32; zero :: () -> EdgeInsets { EdgeInsets.{ top = 0.0, left = 0.0, bottom = 0.0, right = 0.0 }; } all :: (v: f32) -> EdgeInsets { EdgeInsets.{ top = v, left = v, bottom = v, right = v }; } symmetric :: (h: f32, v: f32) -> EdgeInsets { EdgeInsets.{ top = v, left = h, bottom = v, right = h }; } horizontal :: (self: EdgeInsets) -> f32 { self.left + self.right; } vertical :: (self: EdgeInsets) -> f32 { self.top + self.bottom; } } Color :: struct { r, g, b, a: u8; rgba :: (r: u8, g: u8, b: u8, a: u8) -> Color { Color.{ r = r, g = g, b = b, a = a }; } rgb :: (r: u8, g: u8, b: u8) -> Color { Color.{ r = r, g = g, b = b, a = 255 }; } rf :: (self: Color) -> f32 { xx self.r / 255.0; } gf :: (self: Color) -> f32 { xx self.g / 255.0; } bf :: (self: Color) -> f32 { xx self.b / 255.0; } af :: (self: Color) -> f32 { xx self.a / 255.0; } with_alpha :: (self: Color, a: u8) -> Color { Color.{ r = self.r, g = self.g, b = self.b, a = a }; } lerp :: (self: Color, b: Color, t: f32) -> Color { Color.{ r = xx (self.r + (b.r - self.r) * t), g = xx (self.g + (b.g - self.g) * t), b = xx (self.b + (b.b - self.b) * t), a = xx (self.a + (b.a - self.a) * t), }; } } // Named color constants COLOR_WHITE :: Color.{ r = 255, g = 255, b = 255, a = 255 }; COLOR_BLACK :: Color.{ r = 0, g = 0, b = 0, a = 255 }; COLOR_RED :: Color.{ r = 255, g = 59, b = 48, a = 255 }; COLOR_GREEN :: Color.{ r = 52, g = 199, b = 89, a = 255 }; COLOR_BLUE :: Color.{ r = 0, g = 122, b = 255, a = 255 }; COLOR_YELLOW :: Color.{ r = 255, g = 204, b = 0, a = 255 }; COLOR_ORANGE :: Color.{ r = 255, g = 149, b = 0, a = 255 }; COLOR_GRAY :: Color.{ r = 142, g = 142, b = 147, a = 255 }; COLOR_DARK_GRAY :: Color.{ r = 44, g = 44, b = 46, a = 255 }; COLOR_LIGHT_GRAY :: Color.{ r = 209, g = 209, b = 214, a = 255 }; COLOR_TRANSPARENT :: Color.{ r = 0, g = 0, b = 0, a = 0 }; // Size proposal — optional dimensions (null = flexible) ProposedSize :: struct { width: ?f32; height: ?f32; fixed :: (w: f32, h: f32) -> ProposedSize { ProposedSize.{ width = w, height = h }; } flexible :: () -> ProposedSize { ProposedSize.{ width = null, height = null }; } } HAlignment :: enum { leading; center; trailing; } VAlignment :: enum { top; center; bottom; } Alignment :: struct { h: HAlignment; v: VAlignment; } ALIGN_CENTER :: Alignment.{ h = .center, v = .center }; ALIGN_TOP_LEADING :: Alignment.{ h = .leading, v = .top }; ALIGN_TOP :: Alignment.{ h = .center, v = .top }; ALIGN_TOP_TRAILING :: Alignment.{ h = .trailing, v = .top }; ALIGN_LEADING :: Alignment.{ h = .leading, v = .center }; ALIGN_TRAILING :: Alignment.{ h = .trailing, v = .center }; ALIGN_BOTTOM :: Alignment.{ h = .center, v = .bottom }; ALIGN_BOTTOM_LEADING :: Alignment.{ h = .leading, v = .bottom }; ALIGN_BOTTOM_TRAILING :: Alignment.{ h = .trailing, v = .bottom }; // Compute x offset for a child of child_width inside container_width align_h :: (alignment: HAlignment, child_width: f32, container_width: f32) -> f32 { if alignment == { case .leading: 0.0; case .center: { (container_width - child_width) * 0.5; } case .trailing: container_width - child_width; } } // Compute y offset for a child of child_height inside container_height align_v :: (alignment: VAlignment, child_height: f32, container_height: f32) -> f32 { if alignment == { case .top: 0.0; case .center: { (container_height - child_height) * 0.5; } case .bottom: container_height - child_height; } } // --- Lerpable implementations --- #import "ui/animation.sx"; impl Lerpable for Point { lerp :: (self: Point, b: Point, t: f32) -> Point { Point.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t }; } } impl Lerpable for Size { lerp :: (self: Size, b: Size, t: f32) -> Size { Size.{ width = self.width + (b.width - self.width) * t, height = self.height + (b.height - self.height) * t }; } }