From 2492aec9bdcf75fba32fd8f9a44d9d81247e06a0 Mon Sep 17 00:00:00 2001 From: Daniel Tull Date: Mon, 6 Jan 2025 18:19:45 +0000 Subject: [PATCH] AlignmentID --- .../Modifiers/Layout/FixedFrame.swift | 10 ++- Sources/TerminalUI/Primitives/Alignment.swift | 81 +++++++++++++++++-- .../TerminalUI/Primitives/Dimensions.swift | 6 ++ Sources/TerminalUI/Primitives/Size.swift | 8 +- .../Modifiers/Layout/FixedFrameTests.swift | 24 ++++++ 5 files changed, 115 insertions(+), 14 deletions(-) diff --git a/Sources/TerminalUI/Modifiers/Layout/FixedFrame.swift b/Sources/TerminalUI/Modifiers/Layout/FixedFrame.swift index 5a4ffbd..946999f 100644 --- a/Sources/TerminalUI/Modifiers/Layout/FixedFrame.swift +++ b/Sources/TerminalUI/Modifiers/Layout/FixedFrame.swift @@ -3,9 +3,14 @@ extension View { public func frame( width: Horizontal? = nil, - height: Vertical? = nil + height: Vertical? = nil, + alignment: Alignment = .center ) -> some View { - FixedFrame(content: self, width: width, height: height) + FixedFrame( + content: self, + width: width, + height: height, + alignment: alignment) } } @@ -14,6 +19,7 @@ private struct FixedFrame: Builtin, View { let content: Content let width: Horizontal? let height: Vertical? + let alignment: Alignment func size( for proposal: ProposedSize, diff --git a/Sources/TerminalUI/Primitives/Alignment.swift b/Sources/TerminalUI/Primitives/Alignment.swift index 7113c6f..033db62 100644 --- a/Sources/TerminalUI/Primitives/Alignment.swift +++ b/Sources/TerminalUI/Primitives/Alignment.swift @@ -25,18 +25,83 @@ extension Alignment { public static var bottomTrailing: Self { Self(horizontal: .trailing, vertical: .bottom) } } +// MARK: - AlignmentID + +public protocol AlignmentID: Equatable { + static func defaultValue(in size: Size) -> Int +} + +// MARK: - AlignmentKey + +public struct AlignmentKey: Equatable, Hashable, Sendable { + + fileprivate let id: any AlignmentID.Type + + public static func == (lhs: AlignmentKey, rhs: AlignmentKey) -> Bool { + String(describing: lhs.id) == String(describing: rhs.id) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(String(describing: id)) + } +} + // MARK: - Horizontal Alignment -public enum HorizontalAlignment: Equatable, Hashable, Sendable { - case leading - case center - case trailing +public struct HorizontalAlignment: Equatable, Hashable, Sendable { + let id: AlignmentKey +} + +extension HorizontalAlignment { + public init(_ id: any AlignmentID.Type) { + self.id = AlignmentKey(id: id) + } +} + +extension HorizontalAlignment { + public static var leading: Self { Self(HorizontalLeading.self) } + public static var center: Self { Self(HorizontalCenter.self) } + public static var trailing: Self { Self(HorizontalTrailing.self) } +} + +private enum HorizontalLeading: AlignmentID { + static func defaultValue(in size: Size) -> Int { 1 } +} + +private enum HorizontalCenter: AlignmentID { + static func defaultValue(in size: Size) -> Int { Int(size.width) / 2 } +} + +private enum HorizontalTrailing: AlignmentID { + static func defaultValue(in size: Size) -> Int { Int(size.width) } } // MARK: - Vertical Alignment -public enum VerticalAlignment: Equatable, Hashable, Sendable { - case top - case center - case bottom +public struct VerticalAlignment: Equatable, Hashable, Sendable { + let id: AlignmentKey +} + +extension VerticalAlignment { + public init(_ id: any AlignmentID.Type) { + self.id = AlignmentKey(id: id) + } +} + +extension VerticalAlignment { + public static var top: Self { Self(VerticalTop.self) } + public static var center: Self { Self(VerticalCenter.self) } + public static var bottom: Self { Self(VerticalBottom.self) } +} + +private enum VerticalTop: AlignmentID { + static func defaultValue(in size: Size) -> Int { 1 } +} + +private enum VerticalCenter: AlignmentID { + static func defaultValue(in size: Size) -> Int { Int(size.height) / 2 } +} + +private enum VerticalBottom: AlignmentID { + static func defaultValue(in size: Size) -> Int { Int(size.height) } } diff --git a/Sources/TerminalUI/Primitives/Dimensions.swift b/Sources/TerminalUI/Primitives/Dimensions.swift index 8412c87..dfd3f52 100644 --- a/Sources/TerminalUI/Primitives/Dimensions.swift +++ b/Sources/TerminalUI/Primitives/Dimensions.swift @@ -118,3 +118,9 @@ extension Vertical { Vertical(-vertical.value) } } + +extension Int { + init(_ vertical: Vertical) { + self = vertical.value + } +} diff --git a/Sources/TerminalUI/Primitives/Size.swift b/Sources/TerminalUI/Primitives/Size.swift index b09d11b..57c3237 100644 --- a/Sources/TerminalUI/Primitives/Size.swift +++ b/Sources/TerminalUI/Primitives/Size.swift @@ -1,10 +1,10 @@ -package struct Size { +public struct Size { - package let width: Horizontal - package let height: Vertical + public let width: Horizontal + public let height: Vertical - package init(width: Horizontal, height: Vertical) { + public init(width: Horizontal, height: Vertical) { self.width = width self.height = height } diff --git a/Tests/TerminalUITests/Modifiers/Layout/FixedFrameTests.swift b/Tests/TerminalUITests/Modifiers/Layout/FixedFrameTests.swift index 9fefca3..20b199f 100644 --- a/Tests/TerminalUITests/Modifiers/Layout/FixedFrameTests.swift +++ b/Tests/TerminalUITests/Modifiers/Layout/FixedFrameTests.swift @@ -131,4 +131,28 @@ struct FixedFrameTests { Position(x: 2, y: 2): pixel, ]) } + + @Test("alignment", arguments: Array<( + Alignment, Horizontal, Vertical + )>([ + (.topLeading, 1, 1), + (.top, 2, 1), + (.topTrailing, 3, 1), + (.leading, 1, 2), + (.center, 2, 2), + (.trailing, 3, 2), + (.bottomLeading, 1, 3), + (.bottom, 2, 3), + (.bottomTrailing, 3, 3), + ])) + func alignment(alignment: Alignment, x: Horizontal, y: Vertical) { + + canvas.render { + view + .frame(width: 1, height: 1) + .frame(width: 3, height: 3, alignment: alignment) + } + + #expect(canvas.pixels == [Position(x: x, y: y): pixel]) + } }