diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..c5791b5 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,107 @@ +# SwiftLint Configuration for TUIKit + +# Only lint project sources (not build artifacts or dependencies) +included: + - Sources + - Tests + +excluded: + - .build + - .swiftpm + +# Rules +disabled_rules: + # We use fatalError() intentionally in body properties for Renderable views + - fatal_error_message + # Nesting is acceptable for enums inside structs (e.g. Appearance.ID, GeneratedPalette.Hue) + - nesting + # BorderStyle.none follows SwiftUI convention (e.g. Font.Weight.none) + - discouraged_none_name + +opt_in_rules: + - closure_end_indentation + - closure_spacing + - collection_alignment + - comma_inheritance + - contains_over_filter_count + - contains_over_filter_is_empty + - contains_over_first_not_nil + - contains_over_range_nil_comparison + - empty_collection_literal + - empty_count + - empty_string + - enum_case_associated_values_count + - explicit_init + - first_where + - flatmap_over_map_reduce + - identical_operands + - implicitly_unwrapped_optional + - joined_default_parameter + - last_where + - legacy_multiple + - modifier_order + - operator_usage_whitespace + - prefer_self_in_static_references + - prefer_self_type_over_type_of_self + - redundant_nil_coalescing + - redundant_type_annotation + - return_value_from_void_function + - shorthand_optional_binding + - sorted_first_last + - toggle_bool + - unneeded_parentheses_in_closure_argument + - vertical_whitespace_closing_braces + - yoda_condition + +# Rule Configuration +line_length: + warning: 140 + error: 200 + ignores_urls: true + ignores_comments: true + +file_length: + warning: 500 + error: 1200 + ignore_comment_only_lines: true + +type_body_length: + warning: 400 + error: 600 + +function_body_length: + warning: 80 + error: 150 + +function_parameter_count: + warning: 8 + error: 12 + +cyclomatic_complexity: + warning: 15 + error: 25 + +identifier_name: + min_length: + warning: 2 + error: 1 + max_length: + warning: 60 + error: 80 + excluded: + - id + - to + +large_tuple: + warning: 6 + error: 12 + +type_name: + min_length: + warning: 3 + error: 1 + max_length: + warning: 50 + error: 60 + excluded: + - ID diff --git a/Package.resolved b/Package.resolved index 0e94291..93ea774 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "d7399fa1b3074f6a5debef722dbf8a5621cd2171116ce5f4a91b6c3170b14ece", + "originHash" : "6ffad17119186b233c9c58b654f1e56acc6057ddea719a104c85e9ba6691a8f1", "pins" : [ { "identity" : "swift-docc-plugin", @@ -18,6 +18,15 @@ "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", "version" : "1.0.0" } + }, + { + "identity" : "swiftlintplugins", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", + "state" : { + "revision" : "8a4640d14777685ba8f14e832373160498fbab92", + "version" : "0.63.2" + } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index 9f8297b..fb425d1 100644 --- a/Package.swift +++ b/Package.swift @@ -22,14 +22,21 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.3"), + .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.58.2"), ], targets: [ .target( - name: "TUIKit" + name: "TUIKit", + plugins: [ + .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins"), + ] ), .executableTarget( name: "TUIKitExample", - dependencies: ["TUIKit"] + dependencies: ["TUIKit"], + plugins: [ + .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins"), + ] ), .testTarget( name: "TUIKitTests", diff --git a/Sources/TUIKit/App/App.swift b/Sources/TUIKit/App/App.swift index 5b4b526..756df94 100644 --- a/Sources/TUIKit/App/App.swift +++ b/Sources/TUIKit/App/App.swift @@ -59,7 +59,7 @@ public enum QuitBehavior: Sendable { /// Pressing `q` will always exit the application, regardless of /// the current navigation state. case always - + /// Quit only works from the root/main screen. /// /// Pressing `q` will only exit when no context is pushed onto the @@ -92,33 +92,33 @@ public enum QuitBehavior: Sendable { /// ``` public final class StatusBarState: @unchecked Sendable { // MARK: - User Items - + /// Stack of user contexts with their items. private var userContextStack: [(context: String, items: [any StatusBarItemProtocol])] = [] /// Global user items that are always shown (lowest priority). private var userGlobalItems: [any StatusBarItemProtocol] = [] - + // MARK: - System Items Configuration - + /// Whether system items are shown at all. /// /// Set to `false` to hide all system items (quit, help, theme). /// Default is `true`. public var showSystemItems: Bool = true - + /// Whether the appearance item (`a`) is shown. /// /// When `true`, pressing `a` cycles through available appearances (border styles). /// Default is `true`. public var showAppearanceItem: Bool = true - + /// Whether the theme item (`t`) is shown. /// /// When `true`, pressing `t` cycles through available themes. /// Default is `true`. public var showThemeItem: Bool = true - + /// Controls when the quit shortcut (`q`) is active. /// /// - `.always`: Quit works from any screen (default). @@ -127,9 +127,9 @@ public final class StatusBarState: @unchecked Sendable { /// When set to `.rootOnly`, pressing `q` on a subpage does nothing, /// allowing the app to handle navigation (e.g., go back) instead. public var quitBehavior: QuitBehavior = .always - + // MARK: - Appearance - + /// The current status bar style. public var style: StatusBarStyle = .compact @@ -140,20 +140,20 @@ public final class StatusBarState: @unchecked Sendable { public var highlightColor: Color = .cyan /// The label color. - public var labelColor: Color? = nil + public var labelColor: Color? /// Creates a new status bar state. public init() { // System items are built dynamically based on flags } - + // MARK: - System Items Access - + /// Whether we are at the root level (no context pushed). public var isAtRoot: Bool { userContextStack.isEmpty } - + /// Whether quit is currently allowed based on `quitBehavior`. public var isQuitAllowed: Bool { switch quitBehavior { @@ -163,29 +163,29 @@ public final class StatusBarState: @unchecked Sendable { return isAtRoot } } - + /// The current system items based on configuration flags. /// /// Returns items filtered by `showSystemItems`, `showAppearanceItem`, `showThemeItem`, /// and `quitBehavior`. The quit item is only included when quit is allowed. public var currentSystemItems: [StatusBarItem] { guard showSystemItems else { return [] } - + var items: [StatusBarItem] = [] - + // Quit item respects quitBehavior if isQuitAllowed { items.append(SystemStatusBarItem.quit) } - + if showAppearanceItem { items.append(SystemStatusBarItem.appearance) } - + if showThemeItem { items.append(SystemStatusBarItem.theme) } - + return items } @@ -221,7 +221,7 @@ public final class StatusBarState: @unchecked Sendable { internal func setItemsSilently(_ items: [any StatusBarItemProtocol]) { userGlobalItems = items } - + /// The current user items (topmost context or global user items). /// /// Does not include system items. @@ -319,13 +319,13 @@ public final class StatusBarState: @unchecked Sendable { public var currentItems: [any StatusBarItemProtocol] { // Get shortcuts used by user items (for deduplication) let userShortcuts = Set(currentUserItems.map { $0.shortcut }) - + // Filter out system items that are overridden by user items let filteredSystemItems = currentSystemItems.filter { !userShortcuts.contains($0.shortcut) } - + // Sort user items by order, then append system items (fixed order) let sortedUserItems = currentUserItems.sorted { $0.order < $1.order } - + return sortedUserItems + filteredSystemItems } @@ -333,7 +333,7 @@ public final class StatusBarState: @unchecked Sendable { public var hasItems: Bool { !currentItems.isEmpty } - + /// Whether there are any user items (ignoring system items). public var hasUserItems: Bool { !currentUserItems.isEmpty @@ -362,14 +362,12 @@ public final class StatusBarState: @unchecked Sendable { /// - Returns: True if an item with an action handled the event. @discardableResult public func handleKeyEvent(_ event: KeyEvent) -> Bool { - for item in currentItems { - if item.matches(event) { - if let statusBarItem = item as? StatusBarItem { - // Only consume the event if the item has an action - if statusBarItem.hasAction { - statusBarItem.execute() - return true - } + for item in currentItems where item.matches(event) { + if let statusBarItem = item as? StatusBarItem { + // Only consume the event if the item has an action + if statusBarItem.hasAction { + statusBarItem.execute() + return true } } } @@ -381,7 +379,7 @@ public final class StatusBarState: @unchecked Sendable { /// Environment key for accessing the status bar state. private struct StatusBarKey: EnvironmentKey { - static let defaultValue: StatusBarState = StatusBarState() + static let defaultValue = StatusBarState() } extension EnvironmentValues { @@ -410,14 +408,14 @@ extension EnvironmentValues { /// and read from the main loop. A single-word Bool write/read is practically /// atomic on arm64/x86_64. Using `Atomic` from the `Synchronization` /// module would be cleaner but requires macOS 15+. -private nonisolated(unsafe) var needsRerender = false +nonisolated(unsafe) private var needsRerender = false /// Flag set by the SIGINT signal handler to request a graceful shutdown. /// /// The actual cleanup (disabling raw mode, restoring cursor, exiting /// alternate screen) happens in the main loop — signal handlers must /// not call non-async-signal-safe functions like `write()` or `fflush()`. -private nonisolated(unsafe) var needsShutdown = false +nonisolated(unsafe) private var needsShutdown = false // MARK: - App Runner @@ -593,7 +591,7 @@ internal final class AppRunner { ) let buffer = renderToBuffer(statusBarView, context: context) - + // Get background color from palette let bgColor = paletteManager.currentPalette?.background ?? .black let bgCode = ANSIRenderer.backgroundCode(for: bgColor) @@ -603,10 +601,10 @@ internal final class AppRunner { // Write status bar with theme background for (index, line) in buffer.lines.enumerated() { terminal.moveCursor(toRow: row + index, column: 1) - + let visibleWidth = line.strippedLength let padding = max(0, terminalWidth - visibleWidth) - + // Replace all reset codes with "reset + restore background" let lineWithBg = line.replacingOccurrences(of: reset, with: reset + bgCode) let paddedLine = bgCode + lineWithBg + String(repeating: " ", count: padding) + reset @@ -632,17 +630,17 @@ internal final class AppRunner { if statusBar.isQuitAllowed { isRunning = false } - + case .character(let character) where character == "t" || character == "T": // 't' cycles palette (if theme item is enabled) if statusBar.showThemeItem { paletteManager.cycleNext() } - + case .character(let character) where character == "a" || character == "A": // 'a' cycles appearance appearanceManager.cycleNext() - + default: break } @@ -690,25 +688,25 @@ extension WindowGroup: SceneRenderable { let terminal = Terminal.shared let terminalWidth = terminal.width let terminalHeight = context.availableHeight - + // Get background color from palette let bgColor = context.environment.palette.background let bgCode = ANSIRenderer.backgroundCode(for: bgColor) let reset = ANSIRenderer.reset - + // Write buffer to terminal, ensuring consistent background color for row in 0.. Bool { + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.rawId == rhs.rawId && lhs.borderStyle == rhs.borderStyle } } @@ -100,25 +100,25 @@ extension Appearance { /// ``` public struct ID: RawRepresentable, Hashable, Sendable { public let rawValue: String - + public init(rawValue: String) { self.rawValue = rawValue } - + /// Single line borders (┌─┐). - public static let line = ID(rawValue: "line") - + public static let line = Self(rawValue: "line") + /// Rounded corners (╭─╮). - public static let rounded = ID(rawValue: "rounded") - + public static let rounded = Self(rawValue: "rounded") + /// Double-line borders (╔═╗). - public static let doubleLine = ID(rawValue: "doubleLine") - + public static let doubleLine = Self(rawValue: "doubleLine") + /// Heavy/bold borders (┏━┓). - public static let heavy = ID(rawValue: "heavy") - + public static let heavy = Self(rawValue: "heavy") + /// Block/solid borders (███). - public static let block = ID(rawValue: "block") + public static let block = Self(rawValue: "block") } } @@ -129,27 +129,27 @@ extension Appearance { /// /// Uses `BorderStyle.line` with standard box-drawing characters. public static let line = Appearance(id: .line, borderStyle: .line) - + /// Rounded corners (default). /// /// Uses `BorderStyle.rounded` with curved corner characters. public static let rounded = Appearance(id: .rounded, borderStyle: .rounded) - + /// Double-line borders. /// /// Uses `BorderStyle.doubleLine` for a more prominent look. public static let doubleLine = Appearance(id: .doubleLine, borderStyle: .doubleLine) - + /// Heavy/bold borders. /// /// Uses `BorderStyle.heavy` for bold, prominent borders. public static let heavy = Appearance(id: .heavy, borderStyle: .heavy) - + /// Block/solid borders. /// /// Uses `BorderStyle.block` with solid block characters. public static let block = Appearance(id: .block, borderStyle: .block) - + /// The default appearance (rounded). public static let `default`: Appearance = .rounded } @@ -168,7 +168,7 @@ public struct AppearanceRegistry { .heavy, .block ] - + /// Finds an appearance by ID. /// /// - Parameter id: The appearance ID to find. @@ -213,7 +213,7 @@ extension EnvironmentValues { /// Environment key for the appearance manager. private struct AppearanceManagerKey: EnvironmentKey { - static let defaultValue: ThemeManager = ThemeManager( + static let defaultValue = ThemeManager( items: AppearanceRegistry.all, applyToEnvironment: { item in if let appearance = item as? Appearance { diff --git a/Sources/TUIKit/Core/BorderStyle.swift b/Sources/TUIKit/Core/BorderStyle.swift index de4dde3..3583041 100644 --- a/Sources/TUIKit/Core/BorderStyle.swift +++ b/Sources/TUIKit/Core/BorderStyle.swift @@ -27,10 +27,10 @@ public struct BorderStyle: Sendable, Equatable { /// Vertical edge character. public let vertical: Character - + /// Left T-junction character (├). public let leftT: Character - + /// Right T-junction character (┤). public let rightT: Character @@ -67,7 +67,7 @@ public struct BorderStyle: Sendable, Equatable { /// │ Content│ /// └────────┘ /// ``` - public static let line = BorderStyle( + public static let line = Self( topLeft: "┌", topRight: "┐", bottomLeft: "└", @@ -87,7 +87,7 @@ public struct BorderStyle: Sendable, Equatable { /// ║ Content║ /// ╚════════╝ /// ``` - public static let doubleLine = BorderStyle( + public static let doubleLine = Self( topLeft: "╔", topRight: "╗", bottomLeft: "╚", @@ -107,7 +107,7 @@ public struct BorderStyle: Sendable, Equatable { /// │ Content│ /// ╰────────╯ /// ``` - public static let rounded = BorderStyle( + public static let rounded = Self( topLeft: "╭", topRight: "╮", bottomLeft: "╰", @@ -127,7 +127,7 @@ public struct BorderStyle: Sendable, Equatable { /// ┃ Content┃ /// ┗━━━━━━━━┛ /// ``` - public static let heavy = BorderStyle( + public static let heavy = Self( topLeft: "┏", topRight: "┓", bottomLeft: "┗", @@ -152,7 +152,7 @@ public struct BorderStyle: Sendable, Equatable { /// █ Footer █ /// ▀▀▀▀▀▀▀▀▀▀ ← Bottom: upper half block /// ``` - public static let block = BorderStyle( + public static let block = Self( topLeft: "▄", topRight: "▄", bottomLeft: "▀", @@ -162,19 +162,19 @@ public struct BorderStyle: Sendable, Equatable { leftT: "▀", // Header/Body separator (upper half) rightT: "▀" ) - + /// The character used for the bottom edge of block style. /// /// Block style uses different characters for top (▄) and bottom (▀). public static let blockBottomHorizontal: Character = "▀" - + /// The character used for body/footer separator in block style. /// /// Uses lower half block (▄) to create visual separation. public static let blockFooterSeparator: Character = "▄" /// No visible border (space characters). - public static let none = BorderStyle( + public static let none = Self( topLeft: " ", topRight: " ", bottomLeft: " ", @@ -184,7 +184,7 @@ public struct BorderStyle: Sendable, Equatable { leftT: " ", rightT: " " ) - + /// ASCII-only border (+ - |). /// /// Maximum compatibility with all terminals, including those that @@ -197,7 +197,7 @@ public struct BorderStyle: Sendable, Equatable { /// | Content| /// +--------+ /// ``` - public static let ascii = BorderStyle( + public static let ascii = Self( topLeft: "+", topRight: "+", bottomLeft: "+", diff --git a/Sources/TUIKit/Core/Color.swift b/Sources/TUIKit/Core/Color.swift index d8d28bc..1f362ad 100644 --- a/Sources/TUIKit/Core/Color.swift +++ b/Sources/TUIKit/Core/Color.swift @@ -38,77 +38,77 @@ public struct Color: Sendable, Equatable { // MARK: - Standard ANSI Colors /// Black (ANSI 30/40) - public static let black = Color(value: .standard(.black)) + public static let black = Self(value: .standard(.black)) /// Red (ANSI 31/41) - public static let red = Color(value: .standard(.red)) + public static let red = Self(value: .standard(.red)) /// Green (ANSI 32/42) - public static let green = Color(value: .standard(.green)) + public static let green = Self(value: .standard(.green)) /// Yellow (ANSI 33/43) - public static let yellow = Color(value: .standard(.yellow)) + public static let yellow = Self(value: .standard(.yellow)) /// Blue (ANSI 34/44) - public static let blue = Color(value: .standard(.blue)) + public static let blue = Self(value: .standard(.blue)) /// Magenta (ANSI 35/45) - public static let magenta = Color(value: .standard(.magenta)) + public static let magenta = Self(value: .standard(.magenta)) /// Cyan (ANSI 36/46) - public static let cyan = Color(value: .standard(.cyan)) + public static let cyan = Self(value: .standard(.cyan)) /// White (ANSI 37/47) - public static let white = Color(value: .standard(.white)) + public static let white = Self(value: .standard(.white)) /// Default color (terminal default) - public static let `default` = Color(value: .standard(.`default`)) + public static let `default` = Self(value: .standard(.`default`)) // MARK: - Bright ANSI Colors /// Bright black (gray) - public static let brightBlack = Color(value: .bright(.black)) + public static let brightBlack = Self(value: .bright(.black)) /// Bright red - public static let brightRed = Color(value: .bright(.red)) + public static let brightRed = Self(value: .bright(.red)) /// Bright green - public static let brightGreen = Color(value: .bright(.green)) + public static let brightGreen = Self(value: .bright(.green)) /// Bright yellow - public static let brightYellow = Color(value: .bright(.yellow)) + public static let brightYellow = Self(value: .bright(.yellow)) /// Bright blue - public static let brightBlue = Color(value: .bright(.blue)) + public static let brightBlue = Self(value: .bright(.blue)) /// Bright magenta - public static let brightMagenta = Color(value: .bright(.magenta)) + public static let brightMagenta = Self(value: .bright(.magenta)) /// Bright cyan - public static let brightCyan = Color(value: .bright(.cyan)) + public static let brightCyan = Self(value: .bright(.cyan)) /// Bright white - public static let brightWhite = Color(value: .bright(.white)) + public static let brightWhite = Self(value: .bright(.white)) // MARK: - Semantic Colors /// Primary color (default: blue) - public static let primary = Color.blue + public static let primary = Self.blue /// Secondary color (default: gray) - public static let secondary = Color.brightBlack + public static let secondary = Self.brightBlack /// Accent color (default: cyan) - public static let accent = Color.cyan + public static let accent = Self.cyan /// Warning color - public static let warning = Color.yellow + public static let warning = Self.yellow /// Error color - public static let error = Color.red + public static let error = Self.red /// Success color - public static let success = Color.green + public static let success = Self.green // MARK: - Custom Colors @@ -116,8 +116,8 @@ public struct Color: Sendable, Equatable { /// /// - Parameter index: The palette index (0-255). /// - Returns: The corresponding color. - public static func palette(_ index: UInt8) -> Color { - Color(value: .palette256(index)) + public static func palette(_ index: UInt8) -> Self { + Self(value: .palette256(index)) } /// Creates a True Color RGB color. @@ -127,15 +127,15 @@ public struct Color: Sendable, Equatable { /// - green: The green component (0-255). /// - blue: The blue component (0-255). /// - Returns: The RGB color. - public static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> Color { - Color(value: .rgb(red: red, green: green, blue: blue)) + public static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> Self { + Self(value: .rgb(red: red, green: green, blue: blue)) } /// Creates a color from a hex value. /// /// - Parameter hex: The hex value (e.g., 0xFF5500). /// - Returns: The corresponding RGB color. - public static func hex(_ hex: UInt32) -> Color { + public static func hex(_ hex: UInt32) -> Self { let red = UInt8((hex >> 16) & 0xFF) let green = UInt8((hex >> 8) & 0xFF) let blue = UInt8(hex & 0xFF) @@ -148,7 +148,7 @@ public struct Color: Sendable, Equatable { /// /// - Parameter hex: The hex string (e.g., "#FF5500", "F50", "#abc"). /// - Returns: The corresponding RGB color, or nil if invalid. - public static func hex(_ hex: String) -> Color? { + public static func hex(_ hex: String) -> Self? { var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines) // Remove # prefix if present @@ -178,7 +178,7 @@ public struct Color: Sendable, Equatable { /// - saturation: The saturation component (0-100). /// - lightness: The lightness component (0-100). /// - Returns: The corresponding RGB color. - public static func hsl(_ hue: Double, _ saturation: Double, _ lightness: Double) -> Color { + public static func hsl(_ hue: Double, _ saturation: Double, _ lightness: Double) -> Self { let normalizedHue = hue / 360.0 let normalizedSaturation = saturation / 100.0 let normalizedLightness = lightness / 100.0 @@ -198,15 +198,15 @@ public struct Color: Sendable, Equatable { var adjustedHue = hueComponent if adjustedHue < 0 { adjustedHue += 1 } if adjustedHue > 1 { adjustedHue -= 1 } - if adjustedHue < 1/6 { return luminance + (chroma - luminance) * 6 * adjustedHue } - if adjustedHue < 1/2 { return chroma } - if adjustedHue < 2/3 { return luminance + (chroma - luminance) * (2/3 - adjustedHue) * 6 } + if adjustedHue < 1 / 6 { return luminance + (chroma - luminance) * 6 * adjustedHue } + if adjustedHue < 1 / 2 { return chroma } + if adjustedHue < 2 / 3 { return luminance + (chroma - luminance) * (2 / 3 - adjustedHue) * 6 } return luminance } - let red = UInt8(hueToRGB(luminanceFactor, chromaFactor, normalizedHue + 1/3) * 255) + let red = UInt8(hueToRGB(luminanceFactor, chromaFactor, normalizedHue + 1 / 3) * 255) let green = UInt8(hueToRGB(luminanceFactor, chromaFactor, normalizedHue) * 255) - let blue = UInt8(hueToRGB(luminanceFactor, chromaFactor, normalizedHue - 1/3) * 255) + let blue = UInt8(hueToRGB(luminanceFactor, chromaFactor, normalizedHue - 1 / 3) * 255) return .rgb(red, green, blue) } @@ -215,7 +215,7 @@ public struct Color: Sendable, Equatable { /// /// - Parameter amount: The amount to lighten (0-1, default 0.2). /// - Returns: A lighter color. - public func lighter(by amount: Double = 0.2) -> Color { + public func lighter(by amount: Double = 0.2) -> Self { adjusted(by: amount) } @@ -223,7 +223,7 @@ public struct Color: Sendable, Equatable { /// /// - Parameter amount: The amount to darken (0-1, default 0.2). /// - Returns: A darker color. - public func darker(by amount: Double = 0.2) -> Color { + public func darker(by amount: Double = 0.2) -> Self { adjusted(by: -amount) } @@ -233,7 +233,7 @@ public struct Color: Sendable, Equatable { /// /// - Parameter amount: The adjustment amount (-1 to 1). /// - Returns: The adjusted color, or self if not an RGB color. - private func adjusted(by amount: Double) -> Color { + private func adjusted(by amount: Double) -> Self { guard case .rgb(let red, let green, let blue) = value else { return self } @@ -253,7 +253,7 @@ public struct Color: Sendable, Equatable { /// /// - Parameter opacity: The opacity (0-1). /// - Returns: A color simulating the given opacity. - public func opacity(_ opacity: Double) -> Color { + public func opacity(_ opacity: Double) -> Self { guard case .rgb(let red, let green, let blue) = value else { return self } diff --git a/Sources/TUIKit/Core/Environment.swift b/Sources/TUIKit/Core/Environment.swift index b573186..f0119e0 100644 --- a/Sources/TUIKit/Core/Environment.swift +++ b/Sources/TUIKit/Core/Environment.swift @@ -72,7 +72,7 @@ public struct EnvironmentValues: @unchecked Sendable { /// - keyPath: The key path to the value to modify. /// - value: The new value. /// - Returns: A new EnvironmentValues with the modified value. - public func setting(_ keyPath: WritableKeyPath, to value: V) -> EnvironmentValues { + public func setting(_ keyPath: WritableKeyPath, to value: V) -> Self { var copy = self copy[keyPath: keyPath] = value return copy @@ -90,7 +90,7 @@ public final class EnvironmentStorage: @unchecked Sendable { public static let shared = EnvironmentStorage() /// The current environment values. - private var current: EnvironmentValues = EnvironmentValues() + private var current = EnvironmentValues() /// Stack of environments for nested rendering. private var stack: [EnvironmentValues] = [] diff --git a/Sources/TUIKit/Core/Focus.swift b/Sources/TUIKit/Core/Focus.swift index 3bfe4dc..8b33014 100644 --- a/Sources/TUIKit/Core/Focus.swift +++ b/Sources/TUIKit/Core/Focus.swift @@ -250,7 +250,7 @@ public final class FocusManager: @unchecked Sendable { /// Environment key for the focus manager. private struct FocusManagerKey: EnvironmentKey { - static let defaultValue: FocusManager = FocusManager() + static let defaultValue = FocusManager() } extension EnvironmentValues { diff --git a/Sources/TUIKit/Core/KeyEvent.swift b/Sources/TUIKit/Core/KeyEvent.swift index 0894324..6471d8f 100644 --- a/Sources/TUIKit/Core/KeyEvent.swift +++ b/Sources/TUIKit/Core/KeyEvent.swift @@ -67,7 +67,7 @@ public enum Key: Hashable, Sendable { case character(Character) /// Creates a Key from a character if it's a simple character. - public static func from(_ char: Character) -> Key { + public static func from(_ char: Character) -> Self { .character(char) } } @@ -274,10 +274,8 @@ public final class KeyEventDispatcher: @unchecked Sendable { @discardableResult public func dispatch(_ event: KeyEvent) -> Bool { // Process in reverse order (most recent handlers first) - for handler in handlers.reversed() { - if handler(event) { - return true - } + for handler in handlers.reversed() where handler(event) { + return true } return false } diff --git a/Sources/TUIKit/Core/Preferences.swift b/Sources/TUIKit/Core/Preferences.swift index 47b7799..0566e75 100644 --- a/Sources/TUIKit/Core/Preferences.swift +++ b/Sources/TUIKit/Core/Preferences.swift @@ -84,7 +84,7 @@ public struct PreferenceValues: @unchecked Sendable { /// Merges another set of preference values into this one. /// /// - Parameter other: The other preference values to merge. - public mutating func merge(_ other: PreferenceValues) { + public mutating func merge(_ other: Self) { for (key, value) in other.storage { storage[key] = value } @@ -246,8 +246,6 @@ extension OnPreferenceChangeModifier: Renderable { } } - - // MARK: - Common Preference Keys /// A preference key for the navigation title. @@ -272,5 +270,3 @@ public struct AnchorPreferenceKey: PreferenceKey { value.merge(nextValue()) { _, new in new } } } - - diff --git a/Sources/TUIKit/Core/PrimitiveViews.swift b/Sources/TUIKit/Core/PrimitiveViews.swift index de41a9d..0a4a397 100644 --- a/Sources/TUIKit/Core/PrimitiveViews.swift +++ b/Sources/TUIKit/Core/PrimitiveViews.swift @@ -71,5 +71,3 @@ public struct ViewArray: View { fatalError("ViewArray renders its children directly") } } - - diff --git a/Sources/TUIKit/Core/State.swift b/Sources/TUIKit/Core/State.swift index c65da2d..dd35fb4 100644 --- a/Sources/TUIKit/Core/State.swift +++ b/Sources/TUIKit/Core/State.swift @@ -104,7 +104,7 @@ public struct Binding { /// - Parameter value: The constant value. /// - Returns: A binding that always returns the given value. public static func constant(_ value: Value) -> Binding { - Binding(get: { value }, set: { _ in }) + Self(get: { value }, set: { _ in }) } } diff --git a/Sources/TUIKit/Core/Theme.swift b/Sources/TUIKit/Core/Theme.swift index 13fbca4..08b42ed 100644 --- a/Sources/TUIKit/Core/Theme.swift +++ b/Sources/TUIKit/Core/Theme.swift @@ -83,7 +83,7 @@ public protocol Palette: Cyclable { /// Selection highlight color (foreground). var selection: Color { get } - + /// Selection background color (dimmed accent). var selectionBackground: Color { get } @@ -100,7 +100,7 @@ public protocol Palette: Cyclable { /// Status bar shortcut highlight color. var statusBarHighlight: Color { get } - + // MARK: - Container Colors (for block appearance) /// Container body background (used in block appearance). @@ -254,7 +254,7 @@ public enum PaletteColors { /// Selection color. public static var selection: Color { current.selection } - + /// Selection background color. public static var selectionBackground: Color { current.selectionBackground } @@ -269,10 +269,10 @@ public enum PaletteColors { /// Status bar highlight. public static var statusBarHighlight: Color { current.statusBarHighlight } - + /// Container body background (for block appearance). public static var containerBackground: Color { current.containerBackground } - + /// Container header/footer background (for block appearance). public static var containerHeaderBackground: Color { current.containerHeaderBackground } } @@ -291,33 +291,33 @@ public struct GreenPhosphorPalette: Palette { public let background = Color.hex(0x060A07) // App background (darkest) public let backgroundSecondary = Color.hex(0x0E271C) // Container body background (brighter) public let backgroundTertiary = Color.hex(0x0A1B13) // Header/footer background - + // Green phosphor text hierarchy public let foreground = Color.hex(0x33FF33) // Bright green - primary text public let foregroundSecondary = Color.hex(0x27C227) // Medium green - secondary text public let foregroundTertiary = Color.hex(0x1F8F1F) // Dim green - tertiary/muted text - + // Accent colors public let accent = Color.hex(0x66FF66) // Lighter green for highlights public let accentSecondary = Color.hex(0x00CC00) // Darker accent - + // Semantic colors (stay in green family) public let success = Color.hex(0x33FF33) public let warning = Color.hex(0xCCFF33) // Yellow-green public let error = Color.hex(0xFF6633) // Orange-red (contrast) public let info = Color.hex(0x33FFCC) // Cyan-green - + // UI elements public let border = Color.hex(0x2D5A2D) // Subtle green border public let borderFocused = Color.hex(0x33FF33) // Bright when focused public let selection = Color.hex(0x66FF66) // Bright green for selection text public let selectionBackground = Color.hex(0x1A4D1A) // Dark green for selection bar bg - + // Status bar public let statusBarBackground = Color.hex(0x0F2215) // Dark green for status bar public let statusBarForeground = Color.hex(0x2FDD2F) // Slightly dimmer than primary foreground public let statusBarHighlight = Color.hex(0x66FF66) - + // Container colors for block appearance public var containerBackground: Color { backgroundSecondary } // #0E271C - body public var containerHeaderBackground: Color { backgroundTertiary } // #0A1B13 - header/footer @@ -338,33 +338,33 @@ public struct AmberPhosphorPalette: Palette { public let background = Color.hex(0x0A0706) // App background (darkest) public let backgroundSecondary = Color.hex(0x251710) // Container body background (brighter) public let backgroundTertiary = Color.hex(0x1E110E) // Header/footer background - + // Amber phosphor text hierarchy (matching Spotnik) public let foreground = Color.hex(0xFFAA00) // Bright amber - primary text public let foregroundSecondary = Color.hex(0xCC8800) // Medium amber - secondary text public let foregroundTertiary = Color.hex(0x8F6600) // Dim amber - tertiary/muted text - + // Accent colors public let accent = Color.hex(0xFFCC33) // Lighter amber for highlights public let accentSecondary = Color.hex(0xCC9900) // Darker accent - + // Semantic colors (stay in amber family) public let success = Color.hex(0xFFCC00) public let warning = Color.hex(0xFFE066) // Light amber public let error = Color.hex(0xFF6633) // Orange-red (contrast) public let info = Color.hex(0xFFD966) // Light amber - + // UI elements public let border = Color.hex(0x5A4A2D) // Subtle amber border public let borderFocused = Color.hex(0xFFAA00) // Bright when focused public let selection = Color.hex(0xFFCC33) // Bright amber for selection text public let selectionBackground = Color.hex(0x4D3A1F) // Dark amber for selection bar bg - + // Status bar public let statusBarBackground = Color.hex(0x191613) // Same as header/footer public let statusBarForeground = Color.hex(0xFFAA00) public let statusBarHighlight = Color.hex(0xFFCC33) - + // Container colors for block appearance public var containerBackground: Color { backgroundSecondary } // Body public var containerHeaderBackground: Color { backgroundTertiary } // Header/footer @@ -385,33 +385,33 @@ public struct WhitePhosphorPalette: Palette { public let background = Color.hex(0x06070A) // App background (darkest) public let backgroundSecondary = Color.hex(0x111A2A) // Container body background (brighter) public let backgroundTertiary = Color.hex(0x0D131D) // Header/footer background - + // White/gray phosphor text hierarchy public let foreground = Color.hex(0xE8E8E8) // Bright white - primary text public let foregroundSecondary = Color.hex(0xB0B0B0) // Medium gray - secondary text public let foregroundTertiary = Color.hex(0x787878) // Dim gray - tertiary/muted text - + // Accent colors public let accent = Color.hex(0xFFFFFF) // Pure white for highlights public let accentSecondary = Color.hex(0xC0C0C0) // Light gray accent - + // Semantic colors (subtle tints) public let success = Color.hex(0xC0FFC0) // Slight green tint public let warning = Color.hex(0xFFE0A0) // Slight amber tint public let error = Color.hex(0xFFA0A0) // Slight red tint public let info = Color.hex(0xA0D0FF) // Slight blue tint - + // UI elements public let border = Color.hex(0x484848) // Subtle gray border public let borderFocused = Color.hex(0xE8E8E8) // Bright when focused public let selection = Color.hex(0xFFFFFF) // White for selection text public let selectionBackground = Color.hex(0x3A3A3A) // Dark gray for selection bar bg - + // Status bar public let statusBarBackground = Color.hex(0x131619) // Same as header/footer public let statusBarForeground = Color.hex(0xDCDCDC) // Slightly dimmer than primary foreground public let statusBarHighlight = Color.hex(0xFFFFFF) - + // Container colors for block appearance public var containerBackground: Color { backgroundSecondary } // Body public var containerHeaderBackground: Color { backgroundTertiary } // Header/footer @@ -432,33 +432,33 @@ public struct RedPhosphorPalette: Palette { public let background = Color.hex(0x0A0606) // App background (darkest) public let backgroundSecondary = Color.hex(0x281112) // Container body background (brighter) public let backgroundTertiary = Color.hex(0x1E0F10) // Header/footer background - + // Red phosphor text hierarchy public let foreground = Color.hex(0xFF4444) // Bright red - primary text public let foregroundSecondary = Color.hex(0xCC3333) // Medium red - secondary text public let foregroundTertiary = Color.hex(0x8F2222) // Dim red - tertiary/muted text - + // Accent colors public let accent = Color.hex(0xFF6666) // Lighter red for highlights public let accentSecondary = Color.hex(0xCC4444) // Darker accent - + // Semantic colors (stay in red family) public let success = Color.hex(0xFF8080) // Light red (success in red theme) public let warning = Color.hex(0xFFAA66) // Orange public let error = Color.hex(0xFFFFFF) // White (stands out as error) public let info = Color.hex(0xFF9999) // Light red - + // UI elements public let border = Color.hex(0x5A2D2D) // Subtle red border public let borderFocused = Color.hex(0xFF4444) // Bright when focused public let selection = Color.hex(0xFF6666) // Bright red for selection text public let selectionBackground = Color.hex(0x4D1F1F) // Dark red for selection bar bg - + // Status bar public let statusBarBackground = Color.hex(0x191313) // Same as header/footer public let statusBarForeground = Color.hex(0xF23B3B) // Slightly dimmer than primary foreground public let statusBarHighlight = Color.hex(0xFF6666) - + // Container colors for block appearance public var containerBackground: Color { backgroundSecondary } // Body public var containerHeaderBackground: Color { backgroundTertiary } // Header/footer @@ -600,19 +600,19 @@ public struct GeneratedPalette: Palette, Sendable { // --- Semantic: hue-shifted from base --- // success = base + 120° (toward green family) - let successHue = GeneratedPalette.wrapHue(hue + 120) + let successHue = Self.wrapHue(hue + 120) self.success = Color.hsl(successHue, baseSaturation * 0.70, 65) // warning = base + 60° (toward yellow family) - let warningHue = GeneratedPalette.wrapHue(hue + 60) + let warningHue = Self.wrapHue(hue + 60) self.warning = Color.hsl(warningHue, baseSaturation * 0.80, 70) // error = base + 180° (complementary) - let errorHue = GeneratedPalette.wrapHue(hue + 180) + let errorHue = Self.wrapHue(hue + 180) self.error = Color.hsl(errorHue, baseSaturation * 0.85, 65) // info = base − 60° (analogous cool side) - let infoHue = GeneratedPalette.wrapHue(hue - 60) + let infoHue = Self.wrapHue(hue - 60) self.info = Color.hsl(infoHue, baseSaturation * 0.70, 70) // --- UI elements --- @@ -652,9 +652,9 @@ public struct GeneratedPalette: Palette, Sendable { // MARK: - Presets /// A green generated palette — for direct comparison with GreenPhosphorPalette. - public static let green = GeneratedPalette(name: "Gen. Green", hue: Hue.green) + public static let green = Self(name: "Gen. Green", hue: Hue.green) /// A violet generated palette. - public static let violet = GeneratedPalette(name: "Violet", hue: Hue.violet) + public static let violet = Self(name: "Violet", hue: Hue.violet) } // MARK: - Palette Registry @@ -731,7 +731,7 @@ extension Palette where Self == GeneratedPalette { /// Environment key for the palette manager. private struct PaletteManagerKey: EnvironmentKey { - static let defaultValue: ThemeManager = ThemeManager( + static let defaultValue = ThemeManager( items: PaletteRegistry.all, applyToEnvironment: { item in if let palette = item as? any Palette { diff --git a/Sources/TUIKit/Core/TupleViews.swift b/Sources/TUIKit/Core/TupleViews.swift index cc02dcf..018f977 100644 --- a/Sources/TUIKit/Core/TupleViews.swift +++ b/Sources/TUIKit/Core/TupleViews.swift @@ -1,3 +1,4 @@ +// swiftlint:disable large_tuple // // TupleViews.swift // TUIKit @@ -139,3 +140,4 @@ public struct TupleView10 FrameBuffer { let borderColor = color ?? Color.theme.border @@ -66,7 +66,7 @@ extension BorderedView: Renderable { return FrameBuffer(lines: lines) } - + /// Renders with half-block characters for block appearance. private func renderBlockStyle(buffer: FrameBuffer, innerWidth: Int) -> FrameBuffer { let containerBg = Color.theme.containerBackground @@ -81,4 +81,3 @@ extension BorderedView: Renderable { return FrameBuffer(lines: lines) } } - diff --git a/Sources/TUIKit/Modifiers/DimmedModifier.swift b/Sources/TUIKit/Modifiers/DimmedModifier.swift index b9c374a..b7a35ff 100644 --- a/Sources/TUIKit/Modifiers/DimmedModifier.swift +++ b/Sources/TUIKit/Modifiers/DimmedModifier.swift @@ -57,5 +57,3 @@ extension DimmedModifier: Renderable { return ANSIRenderer.dim + text + ANSIRenderer.reset } } - - diff --git a/Sources/TUIKit/Modifiers/FrameModifier.swift b/Sources/TUIKit/Modifiers/FrameModifier.swift index 4dc7cd8..028cee9 100644 --- a/Sources/TUIKit/Modifiers/FrameModifier.swift +++ b/Sources/TUIKit/Modifiers/FrameModifier.swift @@ -165,5 +165,3 @@ extension FlexibleFrameView: Renderable { } } } - - diff --git a/Sources/TUIKit/Modifiers/KeyPressModifier.swift b/Sources/TUIKit/Modifiers/KeyPressModifier.swift index d63534d..b451c33 100644 --- a/Sources/TUIKit/Modifiers/KeyPressModifier.swift +++ b/Sources/TUIKit/Modifiers/KeyPressModifier.swift @@ -46,5 +46,3 @@ extension KeyPressModifier: Renderable { return TUIKit.renderToBuffer(content, context: context) } } - - diff --git a/Sources/TUIKit/Modifiers/LifecycleModifier.swift b/Sources/TUIKit/Modifiers/LifecycleModifier.swift index 997273c..cd90478 100644 --- a/Sources/TUIKit/Modifiers/LifecycleModifier.swift +++ b/Sources/TUIKit/Modifiers/LifecycleModifier.swift @@ -286,5 +286,3 @@ final class TokenGenerator: @unchecked Sendable { return "lifecycle-\(counter)" } } - - diff --git a/Sources/TUIKit/Modifiers/OverlayModifier.swift b/Sources/TUIKit/Modifiers/OverlayModifier.swift index 3b930da..6d44a3d 100644 --- a/Sources/TUIKit/Modifiers/OverlayModifier.swift +++ b/Sources/TUIKit/Modifiers/OverlayModifier.swift @@ -73,5 +73,3 @@ extension OverlayModifier: Renderable { return baseBuffer.composited(with: overlayBuffer, at: (x: horizontalOffset, y: verticalOffset)) } } - - diff --git a/Sources/TUIKit/Modifiers/PaddingModifier.swift b/Sources/TUIKit/Modifiers/PaddingModifier.swift index ea0c85d..82349d2 100644 --- a/Sources/TUIKit/Modifiers/PaddingModifier.swift +++ b/Sources/TUIKit/Modifiers/PaddingModifier.swift @@ -59,16 +59,16 @@ public struct Edge: OptionSet, Sendable { } /// The top edge. - public static let top = Edge(rawValue: 1 << 0) + public static let top = Self(rawValue: 1 << 0) /// The leading (left) edge. - public static let leading = Edge(rawValue: 1 << 1) + public static let leading = Self(rawValue: 1 << 1) /// The bottom edge. - public static let bottom = Edge(rawValue: 1 << 2) + public static let bottom = Self(rawValue: 1 << 2) /// The trailing (right) edge. - public static let trailing = Edge(rawValue: 1 << 3) + public static let trailing = Self(rawValue: 1 << 3) /// All edges. public static let all: Edge = [.top, .leading, .bottom, .trailing] @@ -84,7 +84,7 @@ public struct Edge: OptionSet, Sendable { public struct PaddingModifier: ViewModifier { /// The padding insets. public let insets: EdgeInsets - + /// Creates a padding modifier. /// /// - Parameter insets: The padding insets. @@ -101,7 +101,7 @@ public struct PaddingModifier: ViewModifier { // Calculate line width let lineWidth = buffer.width + insets.leading + insets.trailing let emptyLine = String(repeating: " ", count: lineWidth) - + // Top padding (full lines) for _ in 0.. 0 { lines.append(contentsOf: Array(repeating: "", count: spacing)) } @@ -77,7 +77,7 @@ public struct FrameBuffer { /// - Parameters: /// - other: The buffer to append to the right. /// - spacing: Number of space characters between the two buffers. - public mutating func appendHorizontally(_ other: FrameBuffer, spacing: Int = 0) { + public mutating func appendHorizontally(_ other: Self, spacing: Int = 0) { let maxHeight = max(height, other.height) let myWidth = width let spacer = String(repeating: " ", count: spacing) @@ -100,7 +100,7 @@ public struct FrameBuffer { /// For simplicity, this just overlays line by line. /// /// - Parameter overlay: The buffer to overlay on top. - public mutating func overlay(_ overlay: FrameBuffer) { + public mutating func overlay(_ overlay: Self) { let maxHeight = max(height, overlay.height) var result: [String] = [] for row in 0.. FrameBuffer { + public func composited(with overlay: Self, at position: (x: Int, y: Int)) -> Self { guard !overlay.isEmpty else { return self } let resultWidth = max(width, position.x + overlay.width) @@ -158,7 +158,7 @@ public struct FrameBuffer { result.append(baseLine) } - return FrameBuffer(lines: result) + return Self(lines: result) } /// Inserts overlay text into base text at the specified column position. @@ -206,12 +206,10 @@ public struct FrameBuffer { /// (the parent stack then decides the actual layout direction). /// /// - Parameter buffers: The buffers to stack vertically. - public init(verticallyStacking buffers: [FrameBuffer]) { + public init(verticallyStacking buffers: [Self]) { self.init() for buffer in buffers { appendVertically(buffer) } } } - - diff --git a/Sources/TUIKit/Rendering/Renderable.swift b/Sources/TUIKit/Rendering/Renderable.swift index 235c5a1..e5f462c 100644 --- a/Sources/TUIKit/Rendering/Renderable.swift +++ b/Sources/TUIKit/Rendering/Renderable.swift @@ -58,8 +58,8 @@ public struct RenderContext { /// /// - Parameter environment: The new environment values. /// - Returns: A new RenderContext with the updated environment. - public func withEnvironment(_ environment: EnvironmentValues) -> RenderContext { - RenderContext( + public func withEnvironment(_ environment: EnvironmentValues) -> Self { + Self( terminal: terminal, availableWidth: availableWidth, availableHeight: availableHeight, diff --git a/Sources/TUIKit/Rendering/Terminal.swift b/Sources/TUIKit/Rendering/Terminal.swift index 1a136b6..0ab7633 100644 --- a/Sources/TUIKit/Rendering/Terminal.swift +++ b/Sources/TUIKit/Rendering/Terminal.swift @@ -152,7 +152,7 @@ public final class Terminal: @unchecked Sendable { public func clear() { write(ANSIRenderer.clearScreen + ANSIRenderer.moveCursor(toRow: 1, column: 1)) } - + /// Fills the entire screen with a background color. /// /// This clears the screen and fills every cell with the specified color. @@ -162,15 +162,15 @@ public final class Terminal: @unchecked Sendable { public func fillBackground(_ color: Color) { let size = getSize() let bgCode = ANSIRenderer.backgroundCode(for: color) - + // Move to top-left and fill each line var output = ANSIRenderer.moveCursor(toRow: 1, column: 1) let emptyLine = bgCode + String(repeating: " ", count: size.width) + ANSIRenderer.reset - + for _ in 0.. FrameBuffer { guard buffer.width < width else { return buffer } - + var alignedLines: [String] = [] - + for line in buffer.lines { let lineWidth = line.strippedLength let linePadding = width - lineWidth - + switch alignment { case .leading: // Pad on right @@ -170,7 +170,7 @@ extension VStack: Renderable { alignedLines.append(String(repeating: " ", count: linePadding) + line) } } - + return FrameBuffer(lines: alignedLines) } } @@ -233,7 +233,7 @@ extension TupleView2: Renderable, ChildInfoProvider { func childInfos(context: RenderContext) -> [ChildInfo] { [ makeChildInfo(for: value.0, context: context), - makeChildInfo(for: value.1, context: context), + makeChildInfo(for: value.1, context: context) ] } } @@ -247,7 +247,7 @@ extension TupleView3: Renderable, ChildInfoProvider { [ makeChildInfo(for: value.0, context: context), makeChildInfo(for: value.1, context: context), - makeChildInfo(for: value.2, context: context), + makeChildInfo(for: value.2, context: context) ] } } @@ -262,7 +262,7 @@ extension TupleView4: Renderable, ChildInfoProvider { makeChildInfo(for: value.0, context: context), makeChildInfo(for: value.1, context: context), makeChildInfo(for: value.2, context: context), - makeChildInfo(for: value.3, context: context), + makeChildInfo(for: value.3, context: context) ] } } @@ -278,7 +278,7 @@ extension TupleView5: Renderable, ChildInfoProvider { makeChildInfo(for: value.1, context: context), makeChildInfo(for: value.2, context: context), makeChildInfo(for: value.3, context: context), - makeChildInfo(for: value.4, context: context), + makeChildInfo(for: value.4, context: context) ] } } @@ -295,7 +295,7 @@ extension TupleView6: Renderable, ChildInfoProvider { makeChildInfo(for: value.2, context: context), makeChildInfo(for: value.3, context: context), makeChildInfo(for: value.4, context: context), - makeChildInfo(for: value.5, context: context), + makeChildInfo(for: value.5, context: context) ] } } @@ -313,7 +313,7 @@ extension TupleView7: Renderable, ChildInfoProvider { makeChildInfo(for: value.3, context: context), makeChildInfo(for: value.4, context: context), makeChildInfo(for: value.5, context: context), - makeChildInfo(for: value.6, context: context), + makeChildInfo(for: value.6, context: context) ] } } @@ -332,7 +332,7 @@ extension TupleView8: Renderable, ChildInfoProvider { makeChildInfo(for: value.4, context: context), makeChildInfo(for: value.5, context: context), makeChildInfo(for: value.6, context: context), - makeChildInfo(for: value.7, context: context), + makeChildInfo(for: value.7, context: context) ] } } @@ -352,7 +352,7 @@ extension TupleView9: Renderable, ChildInfoProvider { makeChildInfo(for: value.5, context: context), makeChildInfo(for: value.6, context: context), makeChildInfo(for: value.7, context: context), - makeChildInfo(for: value.8, context: context), + makeChildInfo(for: value.8, context: context) ] } } @@ -373,7 +373,7 @@ extension TupleView10: Renderable, ChildInfoProvider { makeChildInfo(for: value.6, context: context), makeChildInfo(for: value.7, context: context), makeChildInfo(for: value.8, context: context), - makeChildInfo(for: value.9, context: context), + makeChildInfo(for: value.9, context: context) ] } } diff --git a/Sources/TUIKit/Views/Button.swift b/Sources/TUIKit/Views/Button.swift index 22092bb..11943e0 100644 --- a/Sources/TUIKit/Views/Button.swift +++ b/Sources/TUIKit/Views/Button.swift @@ -57,29 +57,29 @@ public struct ButtonStyle: Sendable { // MARK: - Preset Styles /// Default button style with border. - public static let `default` = ButtonStyle() + public static let `default` = Self() /// Primary button style (cyan, bold). - public static let primary = ButtonStyle( + public static let primary = Self( foregroundColor: .cyan, borderColor: .cyan, isBold: true ) /// Destructive button style (red). - public static let destructive = ButtonStyle( + public static let destructive = Self( foregroundColor: .red, borderColor: .red ) /// Success button style (green). - public static let success = ButtonStyle( + public static let success = Self( foregroundColor: .green, borderColor: .green ) /// Plain button style (no border). - public static let plain = ButtonStyle( + public static let plain = Self( borderStyle: BorderStyle.none, horizontalPadding: 0 ) diff --git a/Sources/TUIKit/Views/Card.swift b/Sources/TUIKit/Views/Card.swift index 7a1d5f1..9481f49 100644 --- a/Sources/TUIKit/Views/Card.swift +++ b/Sources/TUIKit/Views/Card.swift @@ -45,10 +45,10 @@ public struct Card: View { /// The card title (optional). public let title: String? - + /// The content of the card. public let content: Content - + /// The footer content (optional). public let footer: Footer? diff --git a/Sources/TUIKit/Views/ContainerView.swift b/Sources/TUIKit/Views/ContainerView.swift index 27645cc..91aee9e 100644 --- a/Sources/TUIKit/Views/ContainerView.swift +++ b/Sources/TUIKit/Views/ContainerView.swift @@ -61,7 +61,7 @@ public struct ContainerConfig: Sendable { } /// Default configuration. - public static let `default` = ContainerConfig() + public static let `default` = Self() } // MARK: - Container Style @@ -75,16 +75,16 @@ public struct ContainerStyle: Sendable { /// Note: Only applies to `Appearance.block`. For other appearances, /// the title is rendered in the top border. public var showHeaderSeparator: Bool - + /// Whether to show a separator line between body and footer. public var showFooterSeparator: Bool - + /// The border style (nil uses appearance default). public var borderStyle: BorderStyle? - + /// The border color (nil uses theme default). public var borderColor: Color? - + /// Creates a container style with the specified options. /// /// - Parameters: @@ -115,7 +115,7 @@ public struct ContainerStyle: Sendable { } /// Default container style. - public static let `default` = ContainerStyle() + public static let `default` = Self() } // MARK: - Render Helper @@ -208,22 +208,22 @@ internal func renderContainer( public struct ContainerView: View { /// The container title (rendered in border or header section). public let title: String? - + /// The title color. public let titleColor: Color? - + /// The main content. public let content: Content - + /// The footer content (typically buttons). public let footer: Footer? - + /// The container style configuration. public let style: ContainerStyle - + /// The inner padding for the body. public let padding: EdgeInsets - + /// Creates a container with all options. /// /// - Parameters: @@ -248,7 +248,7 @@ public struct ContainerView: View { self.content = content() self.footer = footer() } - + public var body: Never { fatalError("ContainerView renders via Renderable") } @@ -289,11 +289,11 @@ extension ContainerView: Renderable { let isBlockAppearance = appearance.rawId == .block let effectiveBorderStyle = style.borderStyle ?? appearance.borderStyle let borderColor = style.borderColor ?? Color.theme.border - + // Render body content let paddedContent = content.padding(padding) let bodyBuffer = TUIKit.renderToBuffer(paddedContent, context: context) - + // Render footer if present let footerBuffer: FrameBuffer? if let footerView = footer { @@ -302,13 +302,13 @@ extension ContainerView: Renderable { } else { footerBuffer = nil } - + // Calculate inner width let titleWidth = title.map { $0.count + 4 } ?? 0 // " Title " + borders let bodyWidth = bodyBuffer.width let footerWidth = footerBuffer?.width ?? 0 let innerWidth = max(titleWidth, bodyWidth, footerWidth) - + if isBlockAppearance { return renderBlockStyle( bodyBuffer: bodyBuffer, @@ -329,9 +329,9 @@ extension ContainerView: Renderable { ) } } - + // MARK: - Standard Style Rendering - + /// Renders with title in top border (line, rounded, doubleLine, heavy). private func renderStandardStyle( bodyBuffer: FrameBuffer, @@ -342,7 +342,7 @@ extension ContainerView: Renderable { context: RenderContext ) -> FrameBuffer { var lines: [String] = [] - + // Top border (with title if present) if let titleText = title { lines.append(BorderRenderer.standardTopBorder( @@ -354,7 +354,7 @@ extension ContainerView: Renderable { style: borderStyle, innerWidth: innerWidth, color: borderColor )) } - + // Body lines with theme background let bodyBg = context.environment.palette.containerBackground for line in bodyBuffer.lines { @@ -363,7 +363,7 @@ extension ContainerView: Renderable { color: borderColor, backgroundColor: bodyBg )) } - + // Footer section (if present) if let footerBuf = footerBuffer, !footerBuf.isEmpty { if style.showFooterSeparator { @@ -371,7 +371,7 @@ extension ContainerView: Renderable { style: borderStyle, innerWidth: innerWidth, color: borderColor )) } - + // Footer lines (no background - footer has its own styling) for line in footerBuf.lines { lines.append(BorderRenderer.standardContentLine( @@ -379,17 +379,17 @@ extension ContainerView: Renderable { )) } } - + // Bottom border lines.append(BorderRenderer.standardBottomBorder( style: borderStyle, innerWidth: innerWidth, color: borderColor )) - + return FrameBuffer(lines: lines) } - + // MARK: - Block Style Rendering - + /// Renders with half-block characters for smooth visual edges. /// /// Block style design: @@ -412,42 +412,42 @@ extension ContainerView: Renderable { context: RenderContext ) -> FrameBuffer { var lines: [String] = [] - + // Get theme colors for block appearance // Header/Footer = darker background // Body = lighter background (containerBackground) let headerFooterBg = Color.theme.containerHeaderBackground let bodyBg = Color.theme.containerBackground - + let hasHeader = title != nil let hasFooter = footerBuffer != nil && !(footerBuffer?.isEmpty ?? true) - + // === TOP BORDER === lines.append(BorderRenderer.blockTopBorder( innerWidth: innerWidth, color: hasHeader ? headerFooterBg : bodyBg )) - + // === HEADER SECTION (if title present) === if let titleText = title { let titleStyled = ANSIRenderer.colorize(" \(titleText) ", foreground: titleColor ?? Color.theme.accent, bold: true) lines.append(BorderRenderer.blockContentLine( content: titleStyled, innerWidth: innerWidth, sectionColor: headerFooterBg )) - + if style.showHeaderSeparator { lines.append(BorderRenderer.blockSeparator( innerWidth: innerWidth, foregroundColor: headerFooterBg, backgroundColor: bodyBg )) } } - + // === BODY LINES === for line in bodyBuffer.lines { lines.append(BorderRenderer.blockContentLine( content: line, innerWidth: innerWidth, sectionColor: bodyBg )) } - + // === FOOTER SECTION (if present) === if let footerBuf = footerBuffer, !footerBuf.isEmpty { if style.showFooterSeparator { @@ -456,19 +456,19 @@ extension ContainerView: Renderable { foregroundColor: headerFooterBg, backgroundColor: bodyBg )) } - + for line in footerBuf.lines { lines.append(BorderRenderer.blockContentLine( content: line, innerWidth: innerWidth, sectionColor: headerFooterBg )) } } - + // === BOTTOM BORDER === lines.append(BorderRenderer.blockBottomBorder( innerWidth: innerWidth, color: hasFooter ? headerFooterBg : bodyBg )) - + return FrameBuffer(lines: lines) } } diff --git a/Sources/TUIKit/Views/Dialog.swift b/Sources/TUIKit/Views/Dialog.swift index c77e5fc..c60e8eb 100644 --- a/Sources/TUIKit/Views/Dialog.swift +++ b/Sources/TUIKit/Views/Dialog.swift @@ -56,7 +56,7 @@ public struct Dialog: View { /// The dialog content. public let content: Content - + /// The footer content (typically buttons). public let footer: Footer? diff --git a/Sources/TUIKit/Views/Menu.swift b/Sources/TUIKit/Views/Menu.swift index de20fd6..bf47a98 100644 --- a/Sources/TUIKit/Views/Menu.swift +++ b/Sources/TUIKit/Views/Menu.swift @@ -180,12 +180,12 @@ extension Menu: Renderable { } var lines: [String] = [] - + // Calculate the content width for full-width selection bar let contentWidth = maxItemWidth + 2 // +2 for padding - + // Track the divider line index (for T-junction rendering) - var dividerLineIndex: Int? = nil + var dividerLineIndex: Int? // Title if present if let menuTitle = title { @@ -204,10 +204,10 @@ extension Menu: Renderable { // Menu items let currentSelection = selectionBinding?.wrappedValue ?? selectedIndex - + for (index, item) in items.enumerated() { let isSelected = index == currentSelection - + // Build the label with optional shortcut let labelText: String if let shortcut = item.shortcut { @@ -218,7 +218,7 @@ extension Menu: Renderable { // Build the full text with padding let fullText = " " + labelText - + // Pad to full width for selection bar let visibleLength = fullText.count let padding = max(0, contentWidth - visibleLength) @@ -248,7 +248,7 @@ extension Menu: Renderable { let appearance = context.environment.appearance let effectiveBorderStyle = borderStyle ?? appearance.borderStyle let isBlockStyle = appearance.rawId == .block - + contentBuffer = applyBorder( to: contentBuffer, style: effectiveBorderStyle, @@ -338,22 +338,22 @@ extension Menu: Renderable { let innerWidth = buffer.width var result: [String] = [] - + if isBlockStyle { let headerFooterBg = Color.theme.containerHeaderBackground let bodyBg = Color.theme.containerBackground let hasHeader = dividerLineIndex != nil - + // Top border result.append(BorderRenderer.blockTopBorder( innerWidth: innerWidth, color: hasHeader ? headerFooterBg : bodyBg )) - + // Content lines with section-aware coloring for (index, line) in buffer.lines.enumerated() { let isHeaderLine = hasHeader && dividerLineIndex.map({ index < $0 }) ?? false let isDividerLine = hasHeader && dividerLineIndex.map({ index == $0 }) ?? false - + if isDividerLine { result.append(BorderRenderer.blockSeparator( innerWidth: innerWidth, foregroundColor: headerFooterBg, backgroundColor: bodyBg @@ -368,7 +368,7 @@ extension Menu: Renderable { )) } } - + // Bottom border result.append(BorderRenderer.blockBottomBorder(innerWidth: innerWidth, color: bodyBg)) } else { @@ -419,5 +419,3 @@ extension AnyView: Renderable { _render(context) } } - - diff --git a/Sources/TUIKit/Views/Panel.swift b/Sources/TUIKit/Views/Panel.swift index 67196ab..db53fa1 100644 --- a/Sources/TUIKit/Views/Panel.swift +++ b/Sources/TUIKit/Views/Panel.swift @@ -49,7 +49,7 @@ public struct Panel: View { /// The content of the panel. public let content: Content - + /// The footer content (typically buttons). public let footer: Footer? diff --git a/Sources/TUIKit/Views/Stacks.swift b/Sources/TUIKit/Views/Stacks.swift index 047fe45..92db27a 100644 --- a/Sources/TUIKit/Views/Stacks.swift +++ b/Sources/TUIKit/Views/Stacks.swift @@ -202,29 +202,29 @@ public struct Alignment: Sendable { // MARK: - Preset Alignments /// Top leading. - public static let topLeading = Alignment(horizontal: .leading, vertical: .top) + public static let topLeading = Self(horizontal: .leading, vertical: .top) /// Top center. - public static let top = Alignment(horizontal: .center, vertical: .top) + public static let top = Self(horizontal: .center, vertical: .top) /// Top trailing. - public static let topTrailing = Alignment(horizontal: .trailing, vertical: .top) + public static let topTrailing = Self(horizontal: .trailing, vertical: .top) /// Center leading. - public static let leading = Alignment(horizontal: .leading, vertical: .center) + public static let leading = Self(horizontal: .leading, vertical: .center) /// Center. - public static let center = Alignment(horizontal: .center, vertical: .center) + public static let center = Self(horizontal: .center, vertical: .center) /// Center trailing. - public static let trailing = Alignment(horizontal: .trailing, vertical: .center) + public static let trailing = Self(horizontal: .trailing, vertical: .center) /// Bottom leading. - public static let bottomLeading = Alignment(horizontal: .leading, vertical: .bottom) + public static let bottomLeading = Self(horizontal: .leading, vertical: .bottom) /// Bottom center. - public static let bottom = Alignment(horizontal: .center, vertical: .bottom) + public static let bottom = Self(horizontal: .center, vertical: .bottom) /// Bottom trailing. - public static let bottomTrailing = Alignment(horizontal: .trailing, vertical: .bottom) + public static let bottomTrailing = Self(horizontal: .trailing, vertical: .bottom) } diff --git a/Sources/TUIKit/Views/StatusBar.swift b/Sources/TUIKit/Views/StatusBar.swift index 08234ab..412e6b7 100644 --- a/Sources/TUIKit/Views/StatusBar.swift +++ b/Sources/TUIKit/Views/StatusBar.swift @@ -263,37 +263,37 @@ public enum Shortcut { /// ``` public struct StatusBarItemOrder: Comparable, Sendable { public let value: Int - + public init(_ value: Int) { self.value = value } - - public static func < (lhs: StatusBarItemOrder, rhs: StatusBarItemOrder) -> Bool { + + public static func < (lhs: Self, rhs: Self) -> Bool { lhs.value < rhs.value } - + // MARK: - User Item Orders - + /// Default order for user-defined items (appears on the left). - public static let `default` = StatusBarItemOrder(500) - + public static let `default` = Self(500) + /// Order for items that should appear early (leftmost user items). - public static let early = StatusBarItemOrder(100) - + public static let early = Self(100) + /// Order for items that should appear late (rightmost user items, before system items). - public static let late = StatusBarItemOrder(800) - + public static let late = Self(800) + // MARK: - System Item Orders (right side) - + /// Order for the quit item (leftmost of system items). /// Appears as: `[...user items] [q quit] [a appearance] [t theme]` - public static let quit = StatusBarItemOrder(900) - + public static let quit = Self(900) + /// Order for the appearance item (middle system item). - public static let appearance = StatusBarItemOrder(910) - + public static let appearance = Self(910) + /// Order for the theme item (rightmost). - public static let theme = StatusBarItemOrder(920) + public static let theme = Self(920) } // MARK: - Status Bar Item Protocol @@ -316,7 +316,7 @@ public protocol StatusBarItemProtocol: Sendable { /// /// Return nil if the item is purely informational (no action). var triggerKey: Key? { get } - + /// The display order of this item. /// /// Items are sorted by order (ascending). Lower values appear first. @@ -332,7 +332,7 @@ public protocol StatusBarItemProtocol: Sendable { public extension StatusBarItemProtocol { /// Default order for user-defined items. var order: StatusBarItemOrder { .default } - + func matches(_ event: KeyEvent) -> Bool { guard let trigger = triggerKey else { return false } return event.key == trigger @@ -488,7 +488,7 @@ public enum SystemStatusBarItem { label: "quit", order: .quit ) - + /// The appearance item (`a appearance`). /// /// Cycles through available appearances (border styles). @@ -498,7 +498,7 @@ public enum SystemStatusBarItem { label: "appearance", order: .appearance ) - + /// The theme item (`t theme`). /// /// Cycles through available themes. Action must be set by the framework. @@ -507,12 +507,12 @@ public enum SystemStatusBarItem { label: "theme", order: .theme ) - + /// All system items in their default order. public static var all: [StatusBarItem] { [quit, appearance, theme] } - + /// Creates system items with custom actions. /// /// - Parameters: @@ -526,7 +526,7 @@ public enum SystemStatusBarItem { onTheme: (@Sendable () -> Void)? = nil ) -> [StatusBarItem] { var result: [StatusBarItem] = [] - + // Quit is always present result.append(StatusBarItem( shortcut: "q", @@ -534,9 +534,9 @@ public enum SystemStatusBarItem { order: .quit, action: onQuit )) - + // Appearance is present if action is provided - if let onAppearance = onAppearance { + if let onAppearance { result.append(StatusBarItem( shortcut: "a", label: "appearance", @@ -544,9 +544,9 @@ public enum SystemStatusBarItem { action: onAppearance )) } - + // Theme is present if action is provided - if let onTheme = onTheme { + if let onTheme { result.append(StatusBarItem( shortcut: "t", label: "theme", @@ -554,7 +554,7 @@ public enum SystemStatusBarItem { action: onTheme )) } - + return result } } @@ -633,7 +633,7 @@ public struct StatusBarItemBuilder { public struct StatusBar: View { /// User items (left container). public let userItems: [any StatusBarItemProtocol] - + /// System items (right container). public let systemItems: [any StatusBarItemProtocol] @@ -673,7 +673,7 @@ public struct StatusBar: View { self.highlightColor = highlightColor self.labelColor = labelColor } - + /// Creates a status bar with all items combined (legacy compatibility). /// /// - Parameters: @@ -719,7 +719,7 @@ public struct StatusBar: View { self.highlightColor = highlightColor self.labelColor = labelColor } - + /// All items combined (sorted user items, then filtered system items). /// /// User items are sorted by their `order` property. @@ -731,7 +731,7 @@ public struct StatusBar: View { let filteredSystemItems = systemItems.filter { !userShortcuts.contains($0.shortcut) } return userItems.sorted { $0.order < $1.order } + filteredSystemItems } - + /// Whether the status bar has any items to display. public var hasItems: Bool { !userItems.isEmpty || !systemItems.isEmpty @@ -748,14 +748,14 @@ extension StatusBar: Renderable { public func renderToBuffer(context: RenderContext) -> FrameBuffer { // Get shortcuts used by user items (for deduplication) let userShortcuts = Set(userItems.map { $0.shortcut }) - + // Filter out system items that are overridden by user items let filteredSystemItems = systemItems.filter { !userShortcuts.contains($0.shortcut) } - + // Combine: sorted user items + filtered system items (fixed order) let sortedUserItems = userItems.sorted { $0.order < $1.order } let combinedItems = sortedUserItems + filteredSystemItems - + guard !combinedItems.isEmpty else { return FrameBuffer() } @@ -912,7 +912,7 @@ extension StatusBar: Renderable { return FrameBuffer(lines: [ BorderRenderer.standardTopBorder(style: border, innerWidth: innerWidth, color: borderColor), BorderRenderer.standardContentLine(content: content, innerWidth: innerWidth, style: border, color: borderColor), - BorderRenderer.standardBottomBorder(style: border, innerWidth: innerWidth, color: borderColor), + BorderRenderer.standardBottomBorder(style: border, innerWidth: innerWidth, color: borderColor) ]) } @@ -923,10 +923,9 @@ extension StatusBar: Renderable { return FrameBuffer(lines: [ BorderRenderer.blockTopBorder(innerWidth: innerWidth, color: statusBarBg), BorderRenderer.blockContentLine(content: content, innerWidth: innerWidth, sectionColor: statusBarBg), - BorderRenderer.blockBottomBorder(innerWidth: innerWidth, color: statusBarBg), + BorderRenderer.blockBottomBorder(innerWidth: innerWidth, color: statusBarBg) ]) } - } // MARK: - Status Bar Height Helper diff --git a/Sources/TUIKitExample/Pages/ContainersPage.swift b/Sources/TUIKitExample/Pages/ContainersPage.swift index b35e312..55fc29e 100644 --- a/Sources/TUIKitExample/Pages/ContainersPage.swift +++ b/Sources/TUIKitExample/Pages/ContainersPage.swift @@ -45,7 +45,7 @@ struct ContainersPage: View { } } } - + HStack(spacing: 2) { // ContainerView with Header and Footer (best for block appearance) DemoSection("ContainerView (Header + Footer)") { @@ -57,7 +57,7 @@ struct ContainersPage: View { Text("Footer: Press Enter to confirm").foregroundColor(.theme.foreground) } } - + // Alignment examples - uses different text lengths to show alignment DemoSection("Content Alignment") { HStack(spacing: 1) { @@ -99,7 +99,7 @@ struct ContainersPage: View { } } } - + DemoSection("Appearance & BorderStyle") { Text("BorderStyle is determined by Appearance. Press 'a' to cycle.").foregroundColor(.theme.foregroundSecondary) } diff --git a/Tests/TUIKitTests/AppearanceTests.swift b/Tests/TUIKitTests/AppearanceTests.swift index ab94b91..bfa579e 100644 --- a/Tests/TUIKitTests/AppearanceTests.swift +++ b/Tests/TUIKitTests/AppearanceTests.swift @@ -12,15 +12,15 @@ import Testing @Suite("Appearance Tests") struct AppearanceTests { - + // MARK: - Appearance ID Tests - + @Test("Appearance ID can be created with rawValue") func appearanceIdRawValue() { let id = Appearance.ID(rawValue: "custom") #expect(id.rawValue == "custom") } - + @Test("Predefined appearance IDs exist") func predefinedAppearanceIds() { #expect(Appearance.ID.line.rawValue == "line") @@ -29,22 +29,22 @@ struct AppearanceTests { #expect(Appearance.ID.heavy.rawValue == "heavy") #expect(Appearance.ID.block.rawValue == "block") } - + @Test("Appearance IDs are hashable") func appearanceIdHashable() { let ids: Set = [.line, .rounded, .doubleLine, .heavy, .block] #expect(ids.count == 5) } - + // MARK: - Appearance Struct Tests - + @Test("Appearance can be created with ID and borderStyle") func appearanceCreation() { let appearance = Appearance(id: .line, borderStyle: .line) #expect(appearance.rawId == .line) #expect(appearance.borderStyle == .line) } - + @Test("Appearance name is derived from ID") func appearanceName() { // .capitalized lowercases after first letter, so "doubleLine" becomes "Doubleline" @@ -54,7 +54,7 @@ struct AppearanceTests { #expect(Appearance.heavy.name == "Heavy") #expect(Appearance.block.name == "Block") } - + @Test("Predefined appearances have correct border styles") func predefinedAppearances() { #expect(Appearance.line.borderStyle == .line) @@ -63,22 +63,22 @@ struct AppearanceTests { #expect(Appearance.heavy.borderStyle == .heavy) #expect(Appearance.block.borderStyle == .block) } - + @Test("Default appearance is rounded") func defaultAppearance() { #expect(Appearance.default.rawId == .rounded) #expect(Appearance.default.borderStyle == .rounded) } - + @Test("Appearances are equatable") func appearanceEquatable() { let appearance1 = Appearance.line let appearance2 = Appearance(id: .line, borderStyle: .line) #expect(appearance1 == appearance2) } - + // MARK: - Appearance Registry Tests - + @Test("AppearanceRegistry contains all predefined appearances") func registryContainsAll() { let all = AppearanceRegistry.all @@ -89,7 +89,7 @@ struct AppearanceTests { #expect(all.contains { $0.rawId == .heavy }) #expect(all.contains { $0.rawId == .block }) } - + @Test("AppearanceRegistry cycling order is correct") func registryCyclingOrder() { let all = AppearanceRegistry.all @@ -100,7 +100,7 @@ struct AppearanceTests { #expect(all[3].rawId == .heavy) #expect(all[4].rawId == .block) } - + @Test("AppearanceRegistry can find appearance by ID") func registryFindById() { let found = AppearanceRegistry.appearance(withId: .heavy) @@ -108,7 +108,7 @@ struct AppearanceTests { #expect(found?.rawId == .heavy) #expect(found?.borderStyle == .heavy) } - + @Test("AppearanceRegistry returns nil for unknown ID") func registryUnknownId() { let customId = Appearance.ID(rawValue: "unknown") @@ -117,7 +117,7 @@ struct AppearanceTests { } // MARK: - Cyclable Conformance - + @Test("Appearance conforms to Cyclable with string id") func cyclableConformance() { let appearance = Appearance.rounded @@ -142,19 +142,19 @@ struct AppearanceManagerTests { let manager = makeAppearanceManager() #expect(manager.items.count == 5) } - + @Test("ThemeManager for appearances starts with first appearance (line)") func managerStartsWithLine() { let manager = makeAppearanceManager() #expect(manager.currentAppearance?.rawId == .line) } - + @Test("ThemeManager for appearances currentName returns capitalized name") func managerCurrentName() { let manager = makeAppearanceManager() #expect(manager.currentName == "Line") } - + @Test("ThemeManager for appearances can be created with custom items") func managerCustomAppearances() { let custom: [Appearance] = [.rounded, .heavy] @@ -168,27 +168,27 @@ struct AppearanceManagerTests { @Suite("Appearance Environment Tests") struct AppearanceEnvironmentTests { - + @Test("Appearance can be accessed via environment") func environmentAccess() { let env = EnvironmentValues() #expect(env.appearance.rawId == .rounded) // Default } - + @Test("Appearance can be set via environment") func environmentSet() { var env = EnvironmentValues() env.appearance = .heavy #expect(env.appearance.rawId == .heavy) } - + @Test("AppearanceManager can be accessed via environment") func managerEnvironmentAccess() { let env = EnvironmentValues() let manager = env.appearanceManager #expect(manager.items.count == 5) } - + @Test("Custom AppearanceManager can be set via environment") func managerEnvironmentSet() { var env = EnvironmentValues() diff --git a/Tests/TUIKitTests/RenderingTests.swift b/Tests/TUIKitTests/RenderingTests.swift index 1e13566..49a6e8b 100644 --- a/Tests/TUIKitTests/RenderingTests.swift +++ b/Tests/TUIKitTests/RenderingTests.swift @@ -51,7 +51,7 @@ struct RenderingTests { let buffer = renderToBuffer(stack, context: context) #expect(buffer.height == 3) #expect(buffer.lines[0] == "A") - #expect(buffer.lines[1] == "") + #expect(buffer.lines[1].isEmpty) #expect(buffer.lines[2] == "B") } diff --git a/Tests/TUIKitTests/StatusBarTests.swift b/Tests/TUIKitTests/StatusBarTests.swift index 420a441..861faea 100644 --- a/Tests/TUIKitTests/StatusBarTests.swift +++ b/Tests/TUIKitTests/StatusBarTests.swift @@ -1,3 +1,4 @@ +// swiftlint:disable file_length // // StatusBarTests.swift // TUIKit @@ -250,7 +251,7 @@ struct StatusBarStateTests { #expect(state.currentItems.count >= 1) #expect(state.currentItems.contains { $0.shortcut == "q" }) } - + @Test("StatusBarState without system items is empty") func stateWithoutSystemItems() { let state = StatusBarState() @@ -508,7 +509,7 @@ struct StatusBarStateTests { state.showSystemItems = false #expect(state.height == 0) } - + @Test("Height is 1 when only system items") func heightWithSystemItems() { let state = StatusBarState() @@ -1021,7 +1022,7 @@ struct StatusBarItemsModifierTests { @Suite("System Status Bar Items Tests") struct SystemStatusBarItemsTests { - + @Test("System items are present by default") func systemItemsPresentByDefault() { let state = StatusBarState() @@ -1029,84 +1030,84 @@ struct SystemStatusBarItemsTests { #expect(state.currentSystemItems.count >= 1) #expect(state.currentSystemItems.contains { $0.shortcut == "q" }) } - + @Test("System items can be disabled") func systemItemsCanBeDisabled() { let state = StatusBarState() state.showSystemItems = false #expect(state.currentSystemItems.isEmpty) } - + @Test("System items appear on the right (high order values)") func systemItemsAppearOnRight() { let state = StatusBarState() state.setItems([ StatusBarItem(shortcut: "s", label: "save") ]) - + // User items should come before system items (lower order) let items = state.currentItems let saveIndex = items.firstIndex { $0.shortcut == "s" } let quitIndex = items.firstIndex { $0.shortcut == "q" } - + #expect(saveIndex != nil) #expect(quitIndex != nil) #expect(saveIndex! < quitIndex!) // save appears before quit } - + @Test("User items can override system items with same shortcut") func userItemsOverrideSystemItems() { let state = StatusBarState() - + // Set user item with same shortcut as system quit state.setItems([ StatusBarItem(shortcut: "q", label: "custom-quit") { // Custom action } ]) - + // Should only have one "q" item, and it should be the user's let qItems = state.currentItems.filter { $0.shortcut == "q" } #expect(qItems.count == 1) #expect(qItems[0].label == "custom-quit") } - + @Test("System item order constants are correct") func systemItemOrderConstants() { // System items should have high order values (900+) #expect(StatusBarItemOrder.quit.value == 900) #expect(StatusBarItemOrder.appearance.value == 910) #expect(StatusBarItemOrder.theme.value == 920) - + // User items should have lower order values #expect(StatusBarItemOrder.default.value == 500) #expect(StatusBarItemOrder.early.value == 100) #expect(StatusBarItemOrder.late.value == 800) - + // User items < system items #expect(StatusBarItemOrder.late < StatusBarItemOrder.quit) } - + @Test("Items are sorted by order") func itemsSortedByOrder() { let state = StatusBarState() - + // Add items in random order state.setItems([ StatusBarItem(shortcut: "l", label: "late", order: .late), StatusBarItem(shortcut: "e", label: "early", order: .early), StatusBarItem(shortcut: "d", label: "default", order: .default) ]) - + let items = state.currentItems - + // Should be sorted: early, default, late, quit (system) let labels = items.map { $0.label } let earlyIndex = labels.firstIndex(of: "early")! let defaultIndex = labels.firstIndex(of: "default")! let lateIndex = labels.firstIndex(of: "late")! let quitIndex = labels.firstIndex(of: "quit")! - + #expect(earlyIndex < defaultIndex) #expect(defaultIndex < lateIndex) #expect(lateIndex < quitIndex)