refactor: Remove T prefix from all public types

- Rename TView → View, TApp → App, TScene → Scene
- Rename TState → State, TViewBuilder → ViewBuilder
- Rename TViewModifier → ViewModifier, TViewArray → ViewArray
- Rename TStatusBar* → StatusBar* (Item, Style, Alignment, Protocol)
- Add Theme system with 8 predefined themes (Phosphor, ncurses, Dark, Light)
- Add Color extensions: hex string, HSL, lighter/darker/opacity
- Update all files, example app, and tests for new names
This commit is contained in:
phranck
2026-01-28 19:08:50 +01:00
parent 5a076dbf9b
commit a53c1a07fd
51 changed files with 1166 additions and 474 deletions
@@ -1,5 +1,5 @@
//
// TApp.swift
// App.swift
// SwiftTUI
//
// The base protocol for SwiftTUI applications.
@@ -7,28 +7,28 @@
import Foundation
// MARK: - TApp Protocol
// MARK: - App Protocol
/// The base protocol for SwiftTUI applications.
///
/// `TApp` is the entry point for every SwiftTUI application,
/// `App` is the entry point for every SwiftTUI application,
/// similar to `App` in SwiftUI.
///
/// # Example
///
/// ```swift
/// @main
/// struct MyApp: TApp {
/// var body: some TScene {
/// struct MyApp: App {
/// var body: some Scene {
/// WindowGroup {
/// ContentView()
/// }
/// }
/// }
/// ```
public protocol TApp {
public protocol App {
/// The type of the main scene.
associatedtype Body: TScene
associatedtype Body: Scene
/// The main scene of the app.
@SceneBuilder
@@ -38,14 +38,14 @@ public protocol TApp {
init()
}
extension TApp {
extension App {
/// Starts the app.
///
/// This method is called by the `@main` attribute and starts
/// the main run loop of the application.
public static func main() {
let app = Self()
let runner = AppRunner(app: app)
let runner = AppRunner<Self>(app: app)
runner.run()
}
}
@@ -60,13 +60,13 @@ extension TApp {
/// # Usage
///
/// ```swift
/// struct MyView: TView {
/// struct MyView: View {
/// @Environment(\.statusBar) var statusBar
///
/// var body: some TView {
/// var body: some View {
/// Button("Action") {
/// statusBar.setItems([
/// TStatusBarItem(shortcut: "⎋", label: "cancel")
/// StatusBarItem(shortcut: "⎋", label: "cancel")
/// ])
/// }
/// }
@@ -74,16 +74,16 @@ extension TApp {
/// ```
public final class StatusBarState: @unchecked Sendable {
/// Stack of contexts with their items.
private var contextStack: [(context: String, items: [any TStatusBarItemProtocol])] = []
private var contextStack: [(context: String, items: [any StatusBarItemProtocol])] = []
/// Global items that are always shown (lowest priority).
private var globalItems: [any TStatusBarItemProtocol] = []
private var globalItems: [any StatusBarItemProtocol] = []
/// The current status bar style.
public var style: TStatusBarStyle = .compact
public var style: StatusBarStyle = .compact
/// The horizontal alignment of items.
public var alignment: TStatusBarAlignment = .justified
public var alignment: StatusBarAlignment = .justified
/// The highlight color for shortcut keys.
public var highlightColor: Color = .cyan
@@ -102,7 +102,7 @@ public final class StatusBarState: @unchecked Sendable {
/// Triggers a re-render.
///
/// - Parameter items: The items to display.
public func setItems(_ items: [any TStatusBarItemProtocol]) {
public func setItems(_ items: [any StatusBarItemProtocol]) {
globalItems = items
AppState.shared.setNeedsRender()
}
@@ -112,7 +112,7 @@ public final class StatusBarState: @unchecked Sendable {
/// Triggers a re-render.
///
/// - Parameter builder: A closure that returns items.
public func setItems(@StatusBarItemBuilder _ builder: () -> [any TStatusBarItemProtocol]) {
public func setItems(@StatusBarItemBuilder _ builder: () -> [any StatusBarItemProtocol]) {
globalItems = builder()
AppState.shared.setNeedsRender()
}
@@ -122,7 +122,7 @@ public final class StatusBarState: @unchecked Sendable {
/// Use this during rendering (e.g., from modifiers) to avoid render loops.
///
/// - Parameter items: The items to display.
internal func setItemsSilently(_ items: [any TStatusBarItemProtocol]) {
internal func setItemsSilently(_ items: [any StatusBarItemProtocol]) {
globalItems = items
}
@@ -136,7 +136,7 @@ public final class StatusBarState: @unchecked Sendable {
/// - Parameters:
/// - context: A unique identifier for this context.
/// - items: The items to display for this context.
public func push(context: String, items: [any TStatusBarItemProtocol]) {
public func push(context: String, items: [any StatusBarItemProtocol]) {
contextStack.removeAll { $0.context == context }
contextStack.append((context, items))
AppState.shared.setNeedsRender()
@@ -149,7 +149,7 @@ public final class StatusBarState: @unchecked Sendable {
/// - Parameters:
/// - context: A unique identifier for this context.
/// - items: The items to display for this context.
internal func pushSilently(context: String, items: [any TStatusBarItemProtocol]) {
internal func pushSilently(context: String, items: [any StatusBarItemProtocol]) {
contextStack.removeAll { $0.context == context }
contextStack.append((context, items))
}
@@ -161,7 +161,7 @@ public final class StatusBarState: @unchecked Sendable {
/// - Parameters:
/// - context: A unique identifier for this context.
/// - builder: A closure that returns items.
public func push(context: String, @StatusBarItemBuilder _ builder: () -> [any TStatusBarItemProtocol]) {
public func push(context: String, @StatusBarItemBuilder _ builder: () -> [any StatusBarItemProtocol]) {
push(context: context, items: builder())
}
@@ -192,7 +192,7 @@ public final class StatusBarState: @unchecked Sendable {
// MARK: - Current State
/// The currently active items (topmost context or global).
public var currentItems: [any TStatusBarItemProtocol] {
public var currentItems: [any StatusBarItemProtocol] {
if let topContext = contextStack.last {
return topContext.items
}
@@ -227,7 +227,7 @@ public final class StatusBarState: @unchecked Sendable {
public func handleKeyEvent(_ event: KeyEvent) -> Bool {
for item in currentItems {
if item.matches(event) {
if let statusBarItem = item as? TStatusBarItem {
if let statusBarItem = item as? StatusBarItem {
// Only consume the event if the item has an action
if statusBarItem.hasAction {
statusBarItem.execute()
@@ -256,7 +256,7 @@ extension EnvironmentValues {
/// @Environment(\.statusBar) var statusBar
///
/// statusBar.setItems([
/// TStatusBarItem(shortcut: "q", label: "quit")
/// StatusBarItem(shortcut: "q", label: "quit")
/// ])
/// ```
public var statusBar: StatusBarState {
@@ -273,14 +273,14 @@ private nonisolated(unsafe) var needsRerender = false
// MARK: - App Runner
/// Runs a TApp.
internal final class AppRunner<App: TApp> {
let app: App
/// Runs an App.
internal final class AppRunner<A: App> {
let app: A
let terminal: Terminal
let statusBar: StatusBarState
private var isRunning = false
init(app: App) {
init(app: A) {
self.app = app
self.terminal = Terminal.shared
self.statusBar = StatusBarState()
@@ -369,7 +369,7 @@ internal final class AppRunner<App: TApp> {
}
}
private func renderScene<S: TScene>(_ scene: S, context: RenderContext) {
private func renderScene<S: Scene>(_ scene: S, context: RenderContext) {
if let renderable = scene as? SceneRenderable {
renderable.renderScene(context: context)
}
@@ -377,7 +377,7 @@ internal final class AppRunner<App: TApp> {
/// Renders the status bar at the specified row.
private func renderStatusBar(atRow row: Int) {
let statusBarView = TStatusBar(
let statusBarView = StatusBar(
items: statusBar.currentItems,
style: statusBar.style,
alignment: statusBar.alignment,
@@ -1,5 +1,5 @@
//
// TScene.swift
// Scene.swift
// SwiftTUI
//
// Scene types for SwiftTUI applications.
@@ -9,7 +9,7 @@
///
/// A scene represents a part of the app structure,
/// typically a window or a group of views.
public protocol TScene {}
public protocol Scene {}
// MARK: - WindowGroup
@@ -24,14 +24,14 @@ public protocol TScene {}
/// ContentView()
/// }
/// ```
public struct WindowGroup<Content: TView>: TScene {
public struct WindowGroup<Content: View>: Scene {
/// The content of the window.
public let content: Content
/// Creates a WindowGroup with the specified content.
///
/// - Parameter content: A ViewBuilder that defines the content.
public init(@TViewBuilder content: () -> Content) {
public init(@ViewBuilder content: () -> Content) {
self.content = content()
}
}
@@ -42,7 +42,7 @@ public struct WindowGroup<Content: TView>: TScene {
@resultBuilder
public struct SceneBuilder {
/// Builds a single scene.
public static func buildBlock<Content: TScene>(_ content: Content) -> Content {
public static func buildBlock<Content: Scene>(_ content: Content) -> Content {
content
}
}
+4 -4
View File
@@ -176,12 +176,12 @@ public final class StorageManager: @unchecked Sendable {
/// # Example
///
/// ```swift
/// struct SettingsView: TView {
/// struct SettingsView: View {
/// @AppStorage("username") var username = "Guest"
/// @AppStorage("darkMode") var darkMode = false
/// @AppStorage("fontSize") var fontSize = 14
///
/// var body: some TView {
/// var body: some View {
/// VStack {
/// Text("User: \(username)")
/// Text("Dark Mode: \(darkMode ? "On" : "Off")")
@@ -306,11 +306,11 @@ extension AppStorage where Value: ExpressibleByNilLiteral {
/// # Example
///
/// ```swift
/// struct ContentView: TView {
/// struct ContentView: View {
/// @SceneStorage("selectedTab") var selectedTab = 0
/// @SceneStorage("scrollOffset") var scrollOffset = 0
///
/// var body: some TView {
/// var body: some View {
/// TabView(selection: $selectedTab) {
/// // ...
/// }
+118
View File
@@ -141,6 +141,124 @@ public struct Color: Sendable, Equatable {
let blue = UInt8(hex & 0xFF)
return .rgb(red, green, blue)
}
/// Creates a color from a hex string.
///
/// Supports formats: "#RGB", "#RRGGBB", "RGB", "RRGGBB"
///
/// - 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? {
var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines)
// Remove # prefix if present
if hexString.hasPrefix("#") {
hexString.removeFirst()
}
// Handle shorthand format (RGB -> RRGGBB)
if hexString.count == 3 {
let chars = Array(hexString)
hexString = String([chars[0], chars[0], chars[1], chars[1], chars[2], chars[2]])
}
// Must be 6 characters now
guard hexString.count == 6 else { return nil }
// Parse hex value
guard let hexValue = UInt32(hexString, radix: 16) else { return nil }
return .hex(hexValue)
}
/// Creates a color from HSL values.
///
/// - Parameters:
/// - hue: The hue component (0-360).
/// - 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 {
let h = hue / 360.0
let s = saturation / 100.0
let l = lightness / 100.0
if s == 0 {
// Achromatic (gray)
let gray = UInt8(l * 255)
return .rgb(gray, gray, gray)
}
let q = l < 0.5 ? l * (1 + s) : l + s - l * s
let p = 2 * l - q
func hueToRGB(_ p: Double, _ q: Double, _ t: Double) -> Double {
var t = t
if t < 0 { t += 1 }
if t > 1 { t -= 1 }
if t < 1/6 { return p + (q - p) * 6 * t }
if t < 1/2 { return q }
if t < 2/3 { return p + (q - p) * (2/3 - t) * 6 }
return p
}
let red = UInt8(hueToRGB(p, q, h + 1/3) * 255)
let green = UInt8(hueToRGB(p, q, h) * 255)
let blue = UInt8(hueToRGB(p, q, h - 1/3) * 255)
return .rgb(red, green, blue)
}
/// Returns a lighter version of this color.
///
/// - Parameter amount: The amount to lighten (0-1, default 0.2).
/// - Returns: A lighter color.
public func lighter(by amount: Double = 0.2) -> Color {
guard case .rgb(let red, let green, let blue) = value else {
return self
}
let newRed = UInt8(min(255, Double(red) + 255 * amount))
let newGreen = UInt8(min(255, Double(green) + 255 * amount))
let newBlue = UInt8(min(255, Double(blue) + 255 * amount))
return .rgb(newRed, newGreen, newBlue)
}
/// Returns a darker version of this color.
///
/// - Parameter amount: The amount to darken (0-1, default 0.2).
/// - Returns: A darker color.
public func darker(by amount: Double = 0.2) -> Color {
guard case .rgb(let red, let green, let blue) = value else {
return self
}
let newRed = UInt8(max(0, Double(red) - 255 * amount))
let newGreen = UInt8(max(0, Double(green) - 255 * amount))
let newBlue = UInt8(max(0, Double(blue) - 255 * amount))
return .rgb(newRed, newGreen, newBlue)
}
/// Returns a color with adjusted opacity (simulated via color mixing).
///
/// Since terminals don't support true transparency, this mixes
/// the color with black to simulate opacity.
///
/// - Parameter opacity: The opacity (0-1).
/// - Returns: A color simulating the given opacity.
public func opacity(_ opacity: Double) -> Color {
guard case .rgb(let red, let green, let blue) = value else {
return self
}
let newRed = UInt8(Double(red) * opacity)
let newGreen = UInt8(Double(green) * opacity)
let newBlue = UInt8(Double(blue) * opacity)
return .rgb(newRed, newGreen, newBlue)
}
}
// MARK: - ANSIColor
+8 -8
View File
@@ -146,13 +146,13 @@ public final class EnvironmentStorage: @unchecked Sendable {
/// # Example
///
/// ```swift
/// struct MyView: TView {
/// struct MyView: View {
/// @Environment(\.statusBar) var statusBar
///
/// var body: some TView {
/// var body: some View {
/// Button("Add Item") {
/// statusBar.push(context: "action") {
/// TStatusBarItem(shortcut: "⎋", label: "cancel")
/// StatusBarItem(shortcut: "⎋", label: "cancel")
/// }
/// }
/// }
@@ -179,7 +179,7 @@ public struct Environment<Value>: @unchecked Sendable {
// MARK: - Environment Modifier
/// A modifier that injects a value into the environment for child views.
public struct EnvironmentModifier<Content: TView, V>: TView {
public struct EnvironmentModifier<Content: View, V>: View {
/// The content view.
let content: Content
@@ -189,7 +189,7 @@ public struct EnvironmentModifier<Content: TView, V>: TView {
/// The value to inject.
let value: V
public var body: some TView {
public var body: some View {
content
}
}
@@ -210,7 +210,7 @@ extension EnvironmentModifier: Renderable {
// MARK: - Internal Rendering Helper
/// Internal helper to render a view (avoids name collision with Renderable.renderToBuffer).
private func renderView<V: TView>(_ view: V, context: RenderContext) -> FrameBuffer {
private func renderView<V: View>(_ view: V, context: RenderContext) -> FrameBuffer {
if let renderable = view as? Renderable {
return renderable.renderToBuffer(context: context)
}
@@ -224,7 +224,7 @@ private func renderView<V: TView>(_ view: V, context: RenderContext) -> FrameBuf
// MARK: - View Extension for Environment
extension TView {
extension View {
/// Sets an environment value for this view and its children.
///
/// - Parameters:
@@ -234,7 +234,7 @@ extension TView {
public func environment<V>(
_ keyPath: WritableKeyPath<EnvironmentValues, V>,
_ value: V
) -> some TView {
) -> some View {
EnvironmentModifier(content: self, keyPath: keyPath, value: value)
}
}
+8 -8
View File
@@ -187,7 +187,7 @@ public final class PreferenceStorage: @unchecked Sendable {
// MARK: - Preference Modifier
/// A modifier that sets a preference value.
public struct PreferenceModifier<Content: TView, K: PreferenceKey>: TView {
public struct PreferenceModifier<Content: View, K: PreferenceKey>: View {
/// The content view.
let content: Content
@@ -212,7 +212,7 @@ extension PreferenceModifier: Renderable {
// MARK: - OnPreferenceChange Modifier
/// A modifier that reacts to preference changes.
public struct OnPreferenceChangeModifier<Content: TView, K: PreferenceKey>: TView
public struct OnPreferenceChangeModifier<Content: View, K: PreferenceKey>: View
where K.Value: Equatable {
/// The content view.
let content: Content
@@ -246,9 +246,9 @@ extension OnPreferenceChangeModifier: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Sets a preference value for this view.
///
/// Preferences propagate up the view hierarchy, allowing child views
@@ -265,7 +265,7 @@ extension TView {
/// - key: The preference key type.
/// - value: The value to set.
/// - Returns: A view that sets the preference.
public func preference<K: PreferenceKey>(key: K.Type, value: K.Value) -> some TView {
public func preference<K: PreferenceKey>(key: K.Type, value: K.Value) -> some View {
PreferenceModifier<Self, K>(content: self, value: value)
}
@@ -289,7 +289,7 @@ extension TView {
public func onPreferenceChange<K: PreferenceKey>(
_ key: K.Type,
perform action: @escaping (K.Value) -> Void
) -> some TView where K.Value: Equatable {
) -> some View where K.Value: Equatable {
OnPreferenceChangeModifier<Self, K>(content: self, action: action)
}
}
@@ -321,7 +321,7 @@ public struct AnchorPreferenceKey: PreferenceKey {
// MARK: - Convenience Extensions
extension TView {
extension View {
/// Sets the navigation title for this view.
///
/// # Example
@@ -335,7 +335,7 @@ extension TView {
///
/// - Parameter title: The navigation title.
/// - Returns: A view with the navigation title preference set.
public func navigationTitle(_ title: String) -> some TView {
public func navigationTitle(_ title: String) -> some View {
preference(key: NavigationTitleKey.self, value: title)
}
}
+15 -15
View File
@@ -5,14 +5,14 @@
// Primitive view types that serve as leaves in the view tree.
//
// MARK: - Never as TView
// MARK: - Never as View
/// `Never` conforms to TView for views that have no body.
/// `Never` conforms to View for views that have no body.
///
/// Primitive views like `Text` or containers like `TupleView` have no
/// body of their own - they are rendered directly. This extension allows
/// using `Never` as the body type.
extension Never: TView {
extension Never: View {
public var body: Never {
fatalError("Never.body should never be called")
}
@@ -32,7 +32,7 @@ extension Never: TView {
/// EmptyView()
/// }
/// ```
public struct EmptyView: TView {
public struct EmptyView: View {
/// Creates an empty view.
public init() {}
@@ -45,8 +45,8 @@ public struct EmptyView: TView {
/// A view that represents either the true or false branch of a conditional.
///
/// This type is used internally by `TViewBuilder` for if-else statements.
public enum ConditionalView<TrueContent: TView, FalseContent: TView>: TView {
/// This type is used internally by `ViewBuilder` for if-else statements.
public enum ConditionalView<TrueContent: View, FalseContent: View>: View {
/// The true branch was executed.
case trueContent(TrueContent)
@@ -58,22 +58,22 @@ public enum ConditionalView<TrueContent: TView, FalseContent: TView>: TView {
}
}
// MARK: - TViewArray
// MARK: - ViewArray
/// A view that contains an array of identical views.
///
/// This type is used internally by `TViewBuilder` for for-in loops.
/// This type is used internally by `ViewBuilder` for for-in loops.
///
/// ```swift
/// ForEach(items) { item in
/// Text(item.name)
/// }
/// ```
public struct TViewArray<Element: TView>: TView {
public struct ViewArray<Element: View>: View {
/// The contained views.
public let elements: [Element]
/// Creates a TViewArray from an array of views.
/// Creates a ViewArray from an array of views.
///
/// - Parameter elements: The views this container holds.
public init(_ elements: [Element]) {
@@ -81,15 +81,15 @@ public struct TViewArray<Element: TView>: TView {
}
public var body: Never {
fatalError("TViewArray renders its children directly")
fatalError("ViewArray renders its children directly")
}
}
// MARK: - Optional TView Conformance
// MARK: - Optional View Conformance
/// Optional views conform to TView when their Wrapped type does.
extension Optional: TView where Wrapped: TView {
public var body: some TView {
/// Optional views conform to View when their Wrapped type does.
extension Optional: View where Wrapped: View {
public var body: some View {
switch self {
case .some(let view):
view
+9 -9
View File
@@ -62,10 +62,10 @@ public final class AppState: @unchecked Sendable {
/// # Example
///
/// ```swift
/// struct ContentView: TView {
/// @TState var selectedIndex = 0
/// struct ContentView: View {
/// @State var selectedIndex = 0
///
/// var body: some TView {
/// var body: some View {
/// Menu(items: menuItems, selection: $selectedIndex)
/// }
/// }
@@ -108,20 +108,20 @@ public struct Binding<Value> {
}
}
// MARK: - TState Property Wrapper
// MARK: - State Property Wrapper
/// A property wrapper that stores mutable state for a view.
///
/// When the value changes, the view hierarchy is re-rendered.
/// Use `@TState` for simple value types owned by a single view.
/// Use `@State` for simple value types owned by a single view.
///
/// # Example
///
/// ```swift
/// struct CounterView: TView {
/// @TState var count = 0
/// struct CounterView: View {
/// @State var count = 0
///
/// var body: some TView {
/// var body: some View {
/// VStack {
/// Text("Count: \(count)")
/// // When count changes, view re-renders
@@ -138,7 +138,7 @@ public struct Binding<Value> {
/// Menu(selection: $selectedIndex)
/// ```
@propertyWrapper
public struct TState<Value> {
public struct State<Value> {
/// The storage for the state value.
private final class Storage {
var value: Value {
+574
View File
@@ -0,0 +1,574 @@
//
// Theme.swift
// SwiftTUI
//
// Theming system with full 16M color support and predefined terminal themes.
//
import Foundation
// MARK: - Theme Protocol
/// A theme defines the color palette for a SwiftTUI application.
///
/// Themes provide semantic colors that views use for consistent styling.
/// SwiftTUI includes several predefined themes inspired by classic terminals.
///
/// # Usage
///
/// ```swift
/// // Set the app theme
/// ThemeManager.shared.current = .amber
///
/// // Use theme colors in views
/// Text("Hello").foregroundColor(.theme.primary)
/// ```
public protocol Theme: Sendable {
/// The theme's unique identifier.
var id: String { get }
/// The theme's display name.
var name: String { get }
// MARK: - Background Colors
/// The primary background color.
var background: Color { get }
/// Secondary background for cards, panels, etc.
var backgroundSecondary: Color { get }
/// Tertiary background for nested elements.
var backgroundTertiary: Color { get }
// MARK: - Foreground Colors
/// Primary text/foreground color.
var foreground: Color { get }
/// Secondary text color (less prominent).
var foregroundSecondary: Color { get }
/// Tertiary text color (even less prominent).
var foregroundTertiary: Color { get }
// MARK: - Accent Colors
/// Primary accent color for interactive elements.
var accent: Color { get }
/// Secondary accent color.
var accentSecondary: Color { get }
// MARK: - Semantic Colors
/// Color for success states.
var success: Color { get }
/// Color for warning states.
var warning: Color { get }
/// Color for error states.
var error: Color { get }
/// Color for informational states.
var info: Color { get }
// MARK: - UI Element Colors
/// Border color for boxes, cards, etc.
var border: Color { get }
/// Border color for focused elements.
var borderFocused: Color { get }
/// Separator/divider color.
var separator: Color { get }
/// Selection highlight color.
var selection: Color { get }
/// Color for disabled elements.
var disabled: Color { get }
// MARK: - Status Bar Colors
/// Status bar background.
var statusBarBackground: Color { get }
/// Status bar text color.
var statusBarForeground: Color { get }
/// Status bar shortcut highlight color.
var statusBarHighlight: Color { get }
}
// MARK: - Default Theme Implementation
extension Theme {
// Default implementations using the primary colors
public var backgroundSecondary: Color { background }
public var backgroundTertiary: Color { background }
public var foregroundSecondary: Color { foreground }
public var foregroundTertiary: Color { foreground }
public var accentSecondary: Color { accent }
public var borderFocused: Color { accent }
public var separator: Color { border }
public var selection: Color { accent }
public var disabled: Color { foregroundTertiary }
public var statusBarBackground: Color { backgroundSecondary }
public var statusBarForeground: Color { foreground }
public var statusBarHighlight: Color { accent }
}
// MARK: - Theme Environment Key
/// Environment key for the current theme.
private struct ThemeKey: EnvironmentKey {
static let defaultValue: Theme = DefaultTheme()
}
extension EnvironmentValues {
/// The current theme.
///
/// Set a theme at the app level and it propagates to all child views:
///
/// ```swift
/// WindowGroup {
/// ContentView()
/// }
/// .environment(\.theme, GreenPhosphorTheme())
/// ```
///
/// Access the theme in views:
///
/// ```swift
/// struct MyView: View {
/// @Environment(\.theme) var theme
///
/// var body: some View {
/// Text("Hello")
/// .foregroundColor(theme.foreground)
/// }
/// }
/// ```
public var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
// MARK: - Color Theme Extension
extension Color {
/// Access theme colors from the current environment.
///
/// These colors read from `EnvironmentStorage.shared` during rendering.
///
/// # Example
///
/// ```swift
/// Text("Hello").foregroundColor(.theme.foreground)
/// ```
public static var theme: ThemeColors.Type {
ThemeColors.self
}
}
/// Namespace for theme-aware colors.
///
/// These properties read the current theme from the environment storage
/// that is set during rendering.
public enum ThemeColors {
/// Gets the current theme from environment storage.
private static var current: Theme {
EnvironmentStorage.shared.environment.theme
}
/// Primary background color.
public static var background: Color { current.background }
/// Secondary background color.
public static var backgroundSecondary: Color { current.backgroundSecondary }
/// Tertiary background color.
public static var backgroundTertiary: Color { current.backgroundTertiary }
/// Primary foreground color.
public static var foreground: Color { current.foreground }
/// Secondary foreground color.
public static var foregroundSecondary: Color { current.foregroundSecondary }
/// Tertiary foreground color.
public static var foregroundTertiary: Color { current.foregroundTertiary }
/// Primary accent color.
public static var accent: Color { current.accent }
/// Secondary accent color.
public static var accentSecondary: Color { current.accentSecondary }
/// Success color.
public static var success: Color { current.success }
/// Warning color.
public static var warning: Color { current.warning }
/// Error color.
public static var error: Color { current.error }
/// Info color.
public static var info: Color { current.info }
/// Border color.
public static var border: Color { current.border }
/// Focused border color.
public static var borderFocused: Color { current.borderFocused }
/// Separator color.
public static var separator: Color { current.separator }
/// Selection color.
public static var selection: Color { current.selection }
/// Disabled color.
public static var disabled: Color { current.disabled }
/// Status bar background.
public static var statusBarBackground: Color { current.statusBarBackground }
/// Status bar foreground.
public static var statusBarForeground: Color { current.statusBarForeground }
/// Status bar highlight.
public static var statusBarHighlight: Color { current.statusBarHighlight }
}
// MARK: - Theme Modifier
extension View {
/// Sets the theme for this view and its descendants.
///
/// # Example
///
/// ```swift
/// ContentView()
/// .theme(GreenPhosphorTheme())
/// ```
///
/// - Parameter theme: The theme to apply.
/// - Returns: A view with the theme applied.
public func theme(_ theme: Theme) -> some View {
environment(\.theme, theme)
}
}
// MARK: - Predefined Themes
/// The default theme using standard ANSI colors.
public struct DefaultTheme: Theme {
public let id = "default"
public let name = "Default"
public let background = Color.default
public let backgroundSecondary = Color.brightBlack
public let foreground = Color.white
public let foregroundSecondary = Color.brightWhite
public let foregroundTertiary = Color.brightBlack
public let accent = Color.cyan
public let accentSecondary = Color.blue
public let success = Color.green
public let warning = Color.yellow
public let error = Color.red
public let info = Color.cyan
public let border = Color.brightBlack
public let statusBarHighlight = Color.cyan
public init() {}
}
/// Classic green phosphor terminal theme (P1 phosphor).
///
/// Inspired by early CRT monitors like the IBM 5151 and Apple II.
public struct GreenPhosphorTheme: Theme {
public let id = "green-phosphor"
public let name = "Green (Phosphor)"
// Dark background with green phosphor glow
public let background = Color.hex(0x0D1F0D)
public let backgroundSecondary = Color.hex(0x0A1A0A)
public let backgroundTertiary = Color.hex(0x071407)
public let foreground = Color.hex(0x33FF33)
public let foregroundSecondary = Color.hex(0x29CC29)
public let foregroundTertiary = Color.hex(0x1F991F)
public let accent = Color.hex(0x66FF66)
public let accentSecondary = Color.hex(0x00CC00)
public let success = Color.hex(0x33FF33)
public let warning = Color.hex(0xCCFF33)
public let error = Color.hex(0xFF6633)
public let info = Color.hex(0x33FFCC)
public let border = Color.hex(0x1F661F)
public let borderFocused = Color.hex(0x33FF33)
public let selection = Color.hex(0x1F4D1F)
public let statusBarBackground = Color.hex(0x0A1A0A)
public let statusBarForeground = Color.hex(0x33FF33)
public let statusBarHighlight = Color.hex(0x66FF66)
public init() {}
}
/// Classic amber phosphor terminal theme (P3 phosphor).
///
/// Inspired by terminals like the IBM 3278 and Wyse 50.
public struct AmberPhosphorTheme: Theme {
public let id = "amber-phosphor"
public let name = "Amber (Phosphor)"
// Dark background with amber phosphor glow
public let background = Color.hex(0x1F1400)
public let backgroundSecondary = Color.hex(0x1A1100)
public let backgroundTertiary = Color.hex(0x140D00)
public let foreground = Color.hex(0xFFB000)
public let foregroundSecondary = Color.hex(0xCC8C00)
public let foregroundTertiary = Color.hex(0x996900)
public let accent = Color.hex(0xFFCC33)
public let accentSecondary = Color.hex(0xCC9900)
public let success = Color.hex(0xFFCC00)
public let warning = Color.hex(0xFFE066)
public let error = Color.hex(0xFF6633)
public let info = Color.hex(0xFFD966)
public let border = Color.hex(0x664D00)
public let borderFocused = Color.hex(0xFFB000)
public let selection = Color.hex(0x4D3A00)
public let statusBarBackground = Color.hex(0x1A1100)
public let statusBarForeground = Color.hex(0xFFB000)
public let statusBarHighlight = Color.hex(0xFFCC33)
public init() {}
}
/// Classic white phosphor terminal theme (P4 phosphor).
///
/// Inspired by terminals like the DEC VT100 and VT220.
public struct WhitePhosphorTheme: Theme {
public let id = "white-phosphor"
public let name = "White (Phosphor)"
// Dark background with white/cool phosphor glow
public let background = Color.hex(0x0A0A0F)
public let backgroundSecondary = Color.hex(0x12121A)
public let backgroundTertiary = Color.hex(0x080810)
public let foreground = Color.hex(0xE0E0E8)
public let foregroundSecondary = Color.hex(0xB0B0B8)
public let foregroundTertiary = Color.hex(0x808088)
public let accent = Color.hex(0xF0F0FF)
public let accentSecondary = Color.hex(0xC0C0D0)
public let success = Color.hex(0xC0FFC0)
public let warning = Color.hex(0xFFE0A0)
public let error = Color.hex(0xFFA0A0)
public let info = Color.hex(0xA0E0FF)
public let border = Color.hex(0x404050)
public let borderFocused = Color.hex(0xE0E0E8)
public let selection = Color.hex(0x303040)
public let statusBarBackground = Color.hex(0x12121A)
public let statusBarForeground = Color.hex(0xE0E0E8)
public let statusBarHighlight = Color.hex(0xF0F0FF)
public init() {}
}
/// Red phosphor terminal theme.
///
/// Less common but used in some military and specialized applications.
public struct RedPhosphorTheme: Theme {
public let id = "red-phosphor"
public let name = "Red (Phosphor)"
// Dark background with red phosphor glow
public let background = Color.hex(0x1A0A0A)
public let backgroundSecondary = Color.hex(0x140808)
public let backgroundTertiary = Color.hex(0x100606)
public let foreground = Color.hex(0xFF4040)
public let foregroundSecondary = Color.hex(0xCC3333)
public let foregroundTertiary = Color.hex(0x992626)
public let accent = Color.hex(0xFF6666)
public let accentSecondary = Color.hex(0xCC4040)
public let success = Color.hex(0xFF8080)
public let warning = Color.hex(0xFFB366)
public let error = Color.hex(0xFFFFFF)
public let info = Color.hex(0xFF9999)
public let border = Color.hex(0x661A1A)
public let borderFocused = Color.hex(0xFF4040)
public let selection = Color.hex(0x4D1414)
public let statusBarBackground = Color.hex(0x140808)
public let statusBarForeground = Color.hex(0xFF4040)
public let statusBarHighlight = Color.hex(0xFF6666)
public init() {}
}
/// Classic ncurses-style theme.
///
/// Traditional terminal colors as used in ncurses applications
/// like htop, mc (Midnight Commander), and vim.
public struct NCursesTheme: Theme {
public let id = "ncurses"
public let name = "ncurses"
// Standard terminal black background
public let background = Color.black
public let backgroundSecondary = Color.blue
public let backgroundTertiary = Color.brightBlack
public let foreground = Color.white
public let foregroundSecondary = Color.brightWhite
public let foregroundTertiary = Color.brightBlack
public let accent = Color.cyan
public let accentSecondary = Color.brightCyan
public let success = Color.green
public let warning = Color.yellow
public let error = Color.red
public let info = Color.cyan
public let border = Color.white
public let borderFocused = Color.brightCyan
public let selection = Color.blue
public let disabled = Color.brightBlack
public let statusBarBackground = Color.blue
public let statusBarForeground = Color.white
public let statusBarHighlight = Color.yellow
public init() {}
}
/// Dark mode theme with modern colors.
public struct DarkTheme: Theme {
public let id = "dark"
public let name = "Dark"
public let background = Color.hex(0x1E1E2E)
public let backgroundSecondary = Color.hex(0x313244)
public let backgroundTertiary = Color.hex(0x45475A)
public let foreground = Color.hex(0xCDD6F4)
public let foregroundSecondary = Color.hex(0xBAC2DE)
public let foregroundTertiary = Color.hex(0xA6ADC8)
public let accent = Color.hex(0x89B4FA)
public let accentSecondary = Color.hex(0x74C7EC)
public let success = Color.hex(0xA6E3A1)
public let warning = Color.hex(0xF9E2AF)
public let error = Color.hex(0xF38BA8)
public let info = Color.hex(0x89DCEB)
public let border = Color.hex(0x585B70)
public let borderFocused = Color.hex(0x89B4FA)
public let selection = Color.hex(0x45475A)
public let statusBarBackground = Color.hex(0x313244)
public let statusBarForeground = Color.hex(0xCDD6F4)
public let statusBarHighlight = Color.hex(0x89B4FA)
public init() {}
}
/// Light mode theme.
public struct LightTheme: Theme {
public let id = "light"
public let name = "Light"
public let background = Color.hex(0xEFF1F5)
public let backgroundSecondary = Color.hex(0xE6E9EF)
public let backgroundTertiary = Color.hex(0xDCE0E8)
public let foreground = Color.hex(0x4C4F69)
public let foregroundSecondary = Color.hex(0x5C5F77)
public let foregroundTertiary = Color.hex(0x6C6F85)
public let accent = Color.hex(0x1E66F5)
public let accentSecondary = Color.hex(0x209FB5)
public let success = Color.hex(0x40A02B)
public let warning = Color.hex(0xDF8E1D)
public let error = Color.hex(0xD20F39)
public let info = Color.hex(0x04A5E5)
public let border = Color.hex(0x9CA0B0)
public let borderFocused = Color.hex(0x1E66F5)
public let selection = Color.hex(0xDCE0E8)
public let statusBarBackground = Color.hex(0xE6E9EF)
public let statusBarForeground = Color.hex(0x4C4F69)
public let statusBarHighlight = Color.hex(0x1E66F5)
public init() {}
}
// MARK: - Theme Registry
/// Registry of available themes.
public struct ThemeRegistry {
/// All available themes.
public static let all: [Theme] = [
DefaultTheme(),
GreenPhosphorTheme(),
AmberPhosphorTheme(),
WhitePhosphorTheme(),
RedPhosphorTheme(),
NCursesTheme(),
DarkTheme(),
LightTheme()
]
/// Finds a theme by ID.
public static func theme(withId id: String) -> Theme? {
all.first { $0.id == id }
}
/// Finds a theme by name.
public static func theme(withName name: String) -> Theme? {
all.first { $0.name == name }
}
}
// MARK: - Convenience Theme Accessors
extension Theme where Self == DefaultTheme {
/// The default theme.
public static var `default`: DefaultTheme { DefaultTheme() }
}
extension Theme where Self == GreenPhosphorTheme {
/// Green phosphor terminal theme.
public static var green: GreenPhosphorTheme { GreenPhosphorTheme() }
/// Green phosphor terminal theme (alias).
public static var greenPhosphor: GreenPhosphorTheme { GreenPhosphorTheme() }
}
extension Theme where Self == AmberPhosphorTheme {
/// Amber phosphor terminal theme.
public static var amber: AmberPhosphorTheme { AmberPhosphorTheme() }
/// Amber phosphor terminal theme (alias).
public static var amberPhosphor: AmberPhosphorTheme { AmberPhosphorTheme() }
}
extension Theme where Self == WhitePhosphorTheme {
/// White phosphor terminal theme.
public static var white: WhitePhosphorTheme { WhitePhosphorTheme() }
/// White phosphor terminal theme (alias).
public static var whitePhosphor: WhitePhosphorTheme { WhitePhosphorTheme() }
}
extension Theme where Self == RedPhosphorTheme {
/// Red phosphor terminal theme.
public static var red: RedPhosphorTheme { RedPhosphorTheme() }
/// Red phosphor terminal theme (alias).
public static var redPhosphor: RedPhosphorTheme { RedPhosphorTheme() }
}
extension Theme where Self == NCursesTheme {
/// Classic ncurses theme.
public static var ncurses: NCursesTheme { NCursesTheme() }
}
extension Theme where Self == DarkTheme {
/// Modern dark theme.
public static var dark: DarkTheme { DarkTheme() }
}
extension Theme where Self == LightTheme {
/// Modern light theme.
public static var light: LightTheme { LightTheme() }
}
+9 -9
View File
@@ -8,7 +8,7 @@
// MARK: - TupleView2
/// A view that contains two child views.
public struct TupleView2<V0: TView, V1: TView>: TView {
public struct TupleView2<V0: View, V1: View>: View {
public let value: (V0, V1)
public init(_ v0: V0, _ v1: V1) {
@@ -23,7 +23,7 @@ public struct TupleView2<V0: TView, V1: TView>: TView {
// MARK: - TupleView3
/// A view that contains three child views.
public struct TupleView3<V0: TView, V1: TView, V2: TView>: TView {
public struct TupleView3<V0: View, V1: View, V2: View>: View {
public let value: (V0, V1, V2)
public init(_ v0: V0, _ v1: V1, _ v2: V2) {
@@ -38,7 +38,7 @@ public struct TupleView3<V0: TView, V1: TView, V2: TView>: TView {
// MARK: - TupleView4
/// A view that contains four child views.
public struct TupleView4<V0: TView, V1: TView, V2: TView, V3: TView>: TView {
public struct TupleView4<V0: View, V1: View, V2: View, V3: View>: View {
public let value: (V0, V1, V2, V3)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3) {
@@ -53,7 +53,7 @@ public struct TupleView4<V0: TView, V1: TView, V2: TView, V3: TView>: TView {
// MARK: - TupleView5
/// A view that contains five child views.
public struct TupleView5<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView>: TView {
public struct TupleView5<V0: View, V1: View, V2: View, V3: View, V4: View>: View {
public let value: (V0, V1, V2, V3, V4)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4) {
@@ -68,7 +68,7 @@ public struct TupleView5<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView>:
// MARK: - TupleView6
/// A view that contains six child views.
public struct TupleView6<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView, V5: TView>: TView {
public struct TupleView6<V0: View, V1: View, V2: View, V3: View, V4: View, V5: View>: View {
public let value: (V0, V1, V2, V3, V4, V5)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5) {
@@ -83,7 +83,7 @@ public struct TupleView6<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView,
// MARK: - TupleView7
/// A view that contains seven child views.
public struct TupleView7<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView, V5: TView, V6: TView>: TView {
public struct TupleView7<V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View>: View {
public let value: (V0, V1, V2, V3, V4, V5, V6)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5, _ v6: V6) {
@@ -98,7 +98,7 @@ public struct TupleView7<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView,
// MARK: - TupleView8
/// A view that contains eight child views.
public struct TupleView8<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView, V5: TView, V6: TView, V7: TView>: TView {
public struct TupleView8<V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View>: View {
public let value: (V0, V1, V2, V3, V4, V5, V6, V7)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5, _ v6: V6, _ v7: V7) {
@@ -113,7 +113,7 @@ public struct TupleView8<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView,
// MARK: - TupleView9
/// A view that contains nine child views.
public struct TupleView9<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView, V5: TView, V6: TView, V7: TView, V8: TView>: TView {
public struct TupleView9<V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View>: View {
public let value: (V0, V1, V2, V3, V4, V5, V6, V7, V8)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5, _ v6: V6, _ v7: V7, _ v8: V8) {
@@ -128,7 +128,7 @@ public struct TupleView9<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView,
// MARK: - TupleView10
/// A view that contains ten child views.
public struct TupleView10<V0: TView, V1: TView, V2: TView, V3: TView, V4: TView, V5: TView, V6: TView, V7: TView, V8: TView, V9: TView>: TView {
public struct TupleView10<V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, V9: View>: View {
public let value: (V0, V1, V2, V3, V4, V5, V6, V7, V8, V9)
public init(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5, _ v6: V6, _ v7: V7, _ v8: V8, _ v9: V9) {
@@ -1,5 +1,5 @@
//
// TView.swift
// View.swift
// SwiftTUI
//
// The base protocol for all SwiftTUI views.
@@ -7,31 +7,31 @@
/// The base protocol for all SwiftTUI views.
///
/// `TView` is the central protocol in SwiftTUI and works similarly to `View` in SwiftUI.
/// `View` is the central protocol in SwiftTUI and works similarly to `View` in SwiftUI.
/// It defines how components declare their structure and content.
///
/// Every TView defines a `body` composed of other TViews.
/// Every View defines a `body` composed of other Views.
/// This enables a hierarchical, declarative UI description.
///
/// # Example
///
/// ```swift
/// struct MyView: TView {
/// var body: some TView {
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, SwiftTUI!")
/// }
/// }
/// ```
public protocol TView {
public protocol View {
/// The type of the body view.
///
/// Swift automatically infers this type from the `body` implementation.
associatedtype Body: TView
associatedtype Body: View
/// The content and behavior of this view.
///
/// Implement this property to define the structure of your view.
/// The body consists of other TViews that together form the UI.
@TViewBuilder
/// The body consists of other Views that together form the UI.
@ViewBuilder
var body: Body { get }
}
@@ -1,13 +1,13 @@
//
// TViewBuilder.swift
// ViewBuilder.swift
// SwiftTUI
//
// Result builder for declarative view composition.
//
/// A result builder for TView hierarchies.
/// A result builder for View hierarchies.
///
/// The `@TViewBuilder` enables a declarative syntax similar to SwiftUI:
/// The `@ViewBuilder` enables a declarative syntax similar to SwiftUI:
///
/// ```swift
/// VStack {
@@ -26,19 +26,19 @@
/// - Optional views (`if let`)
/// - Arrays of views (`for-in`)
@resultBuilder
public struct TViewBuilder {
public struct ViewBuilder {
// MARK: - Single View
/// Builds a single view.
public static func buildBlock<Content: TView>(_ content: Content) -> Content {
public static func buildBlock<Content: View>(_ content: Content) -> Content {
content
}
// MARK: - Multiple Views (Tuple Views)
/// Builds two views into a TupleView.
public static func buildBlock<C0: TView, C1: TView>(
public static func buildBlock<C0: View, C1: View>(
_ c0: C0,
_ c1: C1
) -> TupleView2<C0, C1> {
@@ -46,7 +46,7 @@ public struct TViewBuilder {
}
/// Builds three views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView>(
public static func buildBlock<C0: View, C1: View, C2: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2
@@ -55,7 +55,7 @@ public struct TViewBuilder {
}
/// Builds four views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -65,7 +65,7 @@ public struct TViewBuilder {
}
/// Builds five views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -76,7 +76,7 @@ public struct TViewBuilder {
}
/// Builds six views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView, C5: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View, C5: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -88,7 +88,7 @@ public struct TViewBuilder {
}
/// Builds seven views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView, C5: TView, C6: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -101,7 +101,7 @@ public struct TViewBuilder {
}
/// Builds eight views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView, C5: TView, C6: TView, C7: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -115,7 +115,7 @@ public struct TViewBuilder {
}
/// Builds nine views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView, C5: TView, C6: TView, C7: TView, C8: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -130,7 +130,7 @@ public struct TViewBuilder {
}
/// Builds ten views into a TupleView.
public static func buildBlock<C0: TView, C1: TView, C2: TView, C3: TView, C4: TView, C5: TView, C6: TView, C7: TView, C8: TView, C9: TView>(
public static func buildBlock<C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View>(
_ c0: C0,
_ c1: C1,
_ c2: C2,
@@ -148,45 +148,45 @@ public struct TViewBuilder {
// MARK: - Conditionals
/// Supports the true branch of an if-else.
public static func buildEither<TrueContent: TView, FalseContent: TView>(
public static func buildEither<TrueContent: View, FalseContent: View>(
first content: TrueContent
) -> ConditionalView<TrueContent, FalseContent> {
.trueContent(content)
}
/// Supports the false branch of an if-else.
public static func buildEither<TrueContent: TView, FalseContent: TView>(
public static func buildEither<TrueContent: View, FalseContent: View>(
second content: FalseContent
) -> ConditionalView<TrueContent, FalseContent> {
.falseContent(content)
}
/// Supports optional views (if let, if without else).
public static func buildOptional<Content: TView>(_ content: Content?) -> Content? {
public static func buildOptional<Content: View>(_ content: Content?) -> Content? {
content
}
/// Supports availability limiting.
public static func buildLimitedAvailability<Content: TView>(_ content: Content) -> Content {
public static func buildLimitedAvailability<Content: View>(_ content: Content) -> Content {
content
}
// MARK: - Arrays
/// Supports for-in loops.
public static func buildArray<Content: TView>(_ components: [Content]) -> TViewArray<Content> {
TViewArray(components)
public static func buildArray<Content: View>(_ components: [Content]) -> ViewArray<Content> {
ViewArray(components)
}
// MARK: - Expression
/// Converts a single expression into a view.
public static func buildExpression<Content: TView>(_ expression: Content) -> Content {
public static func buildExpression<Content: View>(_ expression: Content) -> Content {
expression
}
/// Supports optional expressions.
public static func buildExpression<Content: TView>(_ expression: Content?) -> Content? {
public static func buildExpression<Content: View>(_ expression: Content?) -> Content? {
expression
}
}
+7 -7
View File
@@ -7,21 +7,21 @@
/// A modifier that transforms a view's rendered output.
///
/// `TViewModifier` works on the `FrameBuffer` level: it takes a rendered
/// `ViewModifier` works on the `FrameBuffer` level: it takes a rendered
/// buffer and returns a transformed buffer. This allows modifiers like
/// `.padding()` and `.frame()` to manipulate layout after rendering.
///
/// # Example
///
/// ```swift
/// struct MyModifier: TViewModifier {
/// struct MyModifier: ViewModifier {
/// func modify(buffer: FrameBuffer, context: RenderContext) -> FrameBuffer {
/// // transform the buffer
/// return buffer
/// }
/// }
/// ```
public protocol TViewModifier {
public protocol ViewModifier {
/// Transforms a rendered buffer.
///
/// - Parameters:
@@ -37,7 +37,7 @@ public protocol TViewModifier {
///
/// This is the return type of modifier methods like `.frame()` and `.padding()`.
/// It is created automatically — users don't instantiate this directly.
public struct ModifiedView<Content: TView, Modifier: TViewModifier>: TView {
public struct ModifiedView<Content: View, Modifier: ViewModifier>: View {
/// The original view.
public let content: Content
@@ -58,14 +58,14 @@ extension ModifiedView: Renderable {
}
}
// MARK: - TView Modifier Extension
// MARK: - View Modifier Extension
extension TView {
extension View {
/// Applies a modifier to this view.
///
/// - Parameter modifier: The modifier to apply.
/// - Returns: A modified view.
public func modifier<M: TViewModifier>(_ modifier: M) -> ModifiedView<Self, M> {
public func modifier<M: ViewModifier>(_ modifier: M) -> ModifiedView<Self, M> {
ModifiedView(content: self, modifier: modifier)
}
}
@@ -6,7 +6,7 @@
//
/// A modifier that fills the background of a view with a color.
public struct BackgroundModifier: TViewModifier {
public struct BackgroundModifier: ViewModifier {
/// The background color.
public let color: Color
@@ -55,9 +55,9 @@ public struct BackgroundModifier: TViewModifier {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Adds a background color to this view.
///
/// # Example
@@ -9,7 +9,7 @@
///
/// This modifier reduces the available width for content by 2 characters
/// (for the left and right border) to ensure the total width stays within bounds.
public struct BorderedView<Content: TView>: TView {
public struct BorderedView<Content: View>: View {
/// The content to wrap with a border.
let content: Content
@@ -99,9 +99,9 @@ extension BorderedView: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Adds a border around this view.
///
/// The border reserves 2 characters of width (left and right),
@@ -127,18 +127,18 @@ extension TView {
public func border(
_ style: BorderStyle = .line,
color: Color? = nil
) -> some TView {
) -> some View {
BorderedView(content: self, style: style, color: color)
}
}
// MARK: - Legacy TViewModifier (kept for compatibility)
// MARK: - Legacy ViewModifier (kept for compatibility)
/// A modifier that adds a border around a view.
///
/// Note: This is the legacy implementation. The new `BorderedView`
/// correctly handles available width constraints.
public struct BorderModifier: TViewModifier {
public struct BorderModifier: ViewModifier {
/// The border style to use.
public let style: BorderStyle
@@ -9,7 +9,7 @@
///
/// This is useful for de-emphasizing background content when showing
/// overlays, alerts, or dialogs.
public struct DimmedModifier<Content: TView>: TView {
public struct DimmedModifier<Content: View>: View {
/// The content to dim.
let content: Content
@@ -62,9 +62,9 @@ extension DimmedModifier: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Applies a dimming effect to the view content.
///
/// This reduces the visual intensity of the content using the ANSI dim
@@ -81,7 +81,7 @@ extension TView {
/// ```
///
/// - Returns: A view with the dimming effect applied.
public func dimmed() -> some TView {
public func dimmed() -> some View {
DimmedModifier(content: self)
}
}
@@ -25,7 +25,7 @@ public enum FrameDimension: Equatable, Sendable {
///
/// This view handles min/max constraints and renders content with
/// the appropriate available space.
public struct FlexibleFrameView<Content: TView>: TView {
public struct FlexibleFrameView<Content: View>: View {
let content: Content
let minWidth: Int?
let idealWidth: Int?
@@ -171,7 +171,7 @@ extension FlexibleFrameView: Renderable {
/// A modifier that constrains a view to a specific width and/or height.
///
/// Content is aligned within the frame according to the specified alignment.
public struct FrameModifier: TViewModifier {
public struct FrameModifier: ViewModifier {
/// The desired width (nil means intrinsic width).
public let width: Int?
@@ -246,9 +246,9 @@ public struct FrameModifier: TViewModifier {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Sets an explicit frame size for this view.
///
/// The content is aligned within the frame according to the specified alignment.
@@ -310,7 +310,7 @@ extension TView {
idealHeight: Int? = nil,
maxHeight: FrameDimension? = nil,
alignment: Alignment = .center
) -> some TView {
) -> some View {
FlexibleFrameView(
content: self,
minWidth: minWidth,
@@ -9,7 +9,7 @@
///
/// The handler returns a Bool indicating whether the event was consumed.
/// If false is returned, the event continues to propagate to other handlers.
public struct KeyPressModifier<Content: TView>: TView {
public struct KeyPressModifier<Content: View>: View {
/// The content view.
let content: Content
@@ -47,9 +47,9 @@ extension KeyPressModifier: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Adds a handler for key press events.
///
/// The handler is called when any key is pressed while this view
@@ -71,7 +71,7 @@ extension TView {
///
/// - Parameter handler: The handler to call on key press. Returns true if handled.
/// - Returns: A view that handles key presses.
public func onKeyPress(_ handler: @escaping (KeyEvent) -> Bool) -> some TView {
public func onKeyPress(_ handler: @escaping (KeyEvent) -> Bool) -> some View {
KeyPressModifier(content: self, keys: nil, handler: handler)
}
@@ -80,7 +80,7 @@ public final class LifecycleTracker: @unchecked Sendable {
// MARK: - OnAppear Modifier
/// A modifier that executes an action when a view first appears.
public struct OnAppearModifier<Content: TView>: TView {
public struct OnAppearModifier<Content: View>: View {
/// The content view.
let content: Content
@@ -133,7 +133,7 @@ public final class DisappearCallbackStorage: @unchecked Sendable {
}
/// A modifier that executes an action when a view disappears.
public struct OnDisappearModifier<Content: TView>: TView {
public struct OnDisappearModifier<Content: View>: View {
/// The content view.
let content: Content
@@ -166,7 +166,7 @@ extension OnDisappearModifier: Renderable {
/// A modifier that starts an async task when a view appears.
///
/// The task is cancelled when the view disappears.
public struct TaskModifier<Content: TView>: TView {
public struct TaskModifier<Content: View>: View {
/// The content view.
let content: Content
@@ -255,9 +255,9 @@ private final class TokenGenerator: @unchecked Sendable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Executes an action when this view first appears.
///
/// The action is only executed once per view appearance. If the view
@@ -266,8 +266,8 @@ extension TView {
/// # Example
///
/// ```swift
/// struct ContentView: TView {
/// var body: some TView {
/// struct ContentView: View {
/// var body: some View {
/// Text("Hello")
/// .onAppear {
/// loadData()
@@ -278,7 +278,7 @@ extension TView {
///
/// - Parameter action: The action to execute.
/// - Returns: A view that executes the action on appearance.
public func onAppear(perform action: @escaping () -> Void) -> some TView {
public func onAppear(perform action: @escaping () -> Void) -> some View {
OnAppearModifier(
content: self,
token: TokenGenerator.shared.next(),
@@ -293,8 +293,8 @@ extension TView {
/// # Example
///
/// ```swift
/// struct ContentView: TView {
/// var body: some TView {
/// struct ContentView: View {
/// var body: some View {
/// Text("Hello")
/// .onDisappear {
/// cleanup()
@@ -305,7 +305,7 @@ extension TView {
///
/// - Parameter action: The action to execute.
/// - Returns: A view that executes the action on disappearance.
public func onDisappear(perform action: @escaping () -> Void) -> some TView {
public func onDisappear(perform action: @escaping () -> Void) -> some View {
OnDisappearModifier(
content: self,
token: TokenGenerator.shared.next(),
@@ -320,8 +320,8 @@ extension TView {
/// # Example
///
/// ```swift
/// struct ContentView: TView {
/// var body: some TView {
/// struct ContentView: View {
/// var body: some View {
/// Text("Loading...")
/// .task {
/// await fetchData()
@@ -337,7 +337,7 @@ extension TView {
public func task(
priority: TaskPriority = .userInitiated,
_ action: @escaping @Sendable () async -> Void
) -> some TView {
) -> some View {
TaskModifier(
content: self,
token: TokenGenerator.shared.next(),
@@ -10,7 +10,7 @@
/// The overlay is rendered on top of the base content. Both views are rendered
/// to their natural size, and the overlay is positioned according to the
/// specified alignment within the base content's bounds.
public struct OverlayModifier<Base: TView, Overlay: TView>: TView {
public struct OverlayModifier<Base: View, Overlay: View>: View {
/// The base content.
let base: Base
@@ -74,9 +74,9 @@ extension OverlayModifier: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Layers the specified view on top of this view.
///
/// The overlay is positioned according to the specified alignment
@@ -95,10 +95,10 @@ extension TView {
/// - alignment: The alignment of the overlay (default: .center).
/// - content: The overlay content.
/// - Returns: A view with the overlay applied.
public func overlay<Overlay: TView>(
public func overlay<Overlay: View>(
alignment: Alignment = .center,
@TViewBuilder content: () -> Overlay
) -> some TView {
@ViewBuilder content: () -> Overlay
) -> some View {
OverlayModifier(base: self, overlay: content(), alignment: alignment)
}
}
@@ -81,7 +81,7 @@ public struct Edge: OptionSet, Sendable {
}
/// A modifier that adds padding around a view.
public struct PaddingModifier: TViewModifier {
public struct PaddingModifier: ViewModifier {
/// The padding insets.
public let insets: EdgeInsets
@@ -112,9 +112,9 @@ public struct PaddingModifier: TViewModifier {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Adds padding on all sides.
///
/// ```swift
@@ -18,24 +18,24 @@ import Foundation
/// # Example
///
/// ```swift
/// struct MyView: TView {
/// var body: some TView {
/// struct MyView: View {
/// var body: some View {
/// VStack {
/// Text("Content")
/// }
/// .statusBarItems {
/// TStatusBarItem(shortcut: "n", label: "new") { addItem() }
/// TStatusBarItem(shortcut: Shortcut.escape, label: "back") { goBack() }
/// StatusBarItem(shortcut: "n", label: "new") { addItem() }
/// StatusBarItem(shortcut: Shortcut.escape, label: "back") { goBack() }
/// }
/// }
/// }
/// ```
public struct StatusBarItemsModifier<Content: TView>: TView {
public struct StatusBarItemsModifier<Content: View>: View {
/// The content view.
let content: Content
/// The status bar items to display.
let items: [any TStatusBarItemProtocol]
let items: [any StatusBarItemProtocol]
/// Optional context identifier for this view's items.
/// If nil, items are set as global items.
@@ -68,9 +68,9 @@ extension StatusBarItemsModifier: Renderable {
}
}
// MARK: - TView Extension
// MARK: - View Extension
extension TView {
extension View {
/// Sets the status bar items for this view.
///
/// When this view is rendered, the specified items will be displayed
@@ -79,14 +79,14 @@ extension TView {
/// # Example
///
/// ```swift
/// struct MainView: TView {
/// var body: some TView {
/// struct MainView: View {
/// var body: some View {
/// VStack {
/// Text("Main Content")
/// }
/// .statusBarItems([
/// TStatusBarItem(shortcut: "q", label: "quit"),
/// TStatusBarItem(shortcut: "h", label: "help") { showHelp() }
/// StatusBarItem(shortcut: "q", label: "quit"),
/// StatusBarItem(shortcut: "h", label: "help") { showHelp() }
/// ])
/// }
/// }
@@ -94,7 +94,7 @@ extension TView {
///
/// - Parameter items: The status bar items to display.
/// - Returns: A view that sets the specified status bar items.
public func statusBarItems(_ items: [any TStatusBarItemProtocol]) -> some TView {
public func statusBarItems(_ items: [any StatusBarItemProtocol]) -> some View {
StatusBarItemsModifier(content: self, items: items, context: nil)
}
@@ -103,14 +103,14 @@ extension TView {
/// # Example
///
/// ```swift
/// struct MainView: TView {
/// var body: some TView {
/// struct MainView: View {
/// var body: some View {
/// VStack {
/// Text("Main Content")
/// }
/// .statusBarItems {
/// TStatusBarItem(shortcut: "q", label: "quit")
/// TStatusBarItem(shortcut: "h", label: "help") { showHelp() }
/// StatusBarItem(shortcut: "q", label: "quit")
/// StatusBarItem(shortcut: "h", label: "help") { showHelp() }
/// }
/// }
/// }
@@ -119,8 +119,8 @@ extension TView {
/// - Parameter builder: A closure that returns the status bar items.
/// - Returns: A view that sets the specified status bar items.
public func statusBarItems(
@StatusBarItemBuilder _ builder: () -> [any TStatusBarItemProtocol]
) -> some TView {
@StatusBarItemBuilder _ builder: () -> [any StatusBarItemProtocol]
) -> some View {
StatusBarItemsModifier(content: self, items: builder(), context: nil)
}
@@ -133,14 +133,14 @@ extension TView {
/// # Example
///
/// ```swift
/// struct DialogView: TView {
/// var body: some TView {
/// struct DialogView: View {
/// var body: some View {
/// Card {
/// Text("Are you sure?")
/// }
/// .statusBarItems(context: "confirm-dialog") {
/// TStatusBarItem(shortcut: "y", label: "yes") { confirm() }
/// TStatusBarItem(shortcut: "n", label: "no") { cancel() }
/// StatusBarItem(shortcut: "y", label: "yes") { confirm() }
/// StatusBarItem(shortcut: "n", label: "no") { cancel() }
/// }
/// }
/// }
@@ -152,8 +152,8 @@ extension TView {
/// - Returns: A view that pushes status bar items to the context stack.
public func statusBarItems(
context: String,
@StatusBarItemBuilder _ builder: () -> [any TStatusBarItemProtocol]
) -> some TView {
@StatusBarItemBuilder _ builder: () -> [any StatusBarItemProtocol]
) -> some View {
StatusBarItemsModifier(content: self, items: builder(), context: context)
}
@@ -165,8 +165,8 @@ extension TView {
/// - Returns: A view that pushes status bar items to the context stack.
public func statusBarItems(
context: String,
items: [any TStatusBarItemProtocol]
) -> some TView {
items: [any StatusBarItemProtocol]
) -> some View {
StatusBarItemsModifier(content: self, items: items, context: context)
}
}
+2 -2
View File
@@ -70,14 +70,14 @@ public struct RenderContext {
// MARK: - Rendering Helper
/// Renders any TView into a FrameBuffer by checking for Renderable conformance
/// Renders any View into a FrameBuffer by checking for Renderable conformance
/// or recursively rendering the body.
///
/// - Parameters:
/// - view: The view to render.
/// - context: The rendering context.
/// - Returns: A FrameBuffer with the rendered content.
public func renderToBuffer<V: TView>(_ view: V, context: RenderContext) -> FrameBuffer {
public func renderToBuffer<V: View>(_ view: V, context: RenderContext) -> FrameBuffer {
if let renderable = view as? Renderable {
return renderable.renderToBuffer(context: context)
}
@@ -2,12 +2,12 @@
// ViewRenderer.swift
// SwiftTUI
//
// Renders TViews to terminal output via FrameBuffer.
// Renders Views to terminal output via FrameBuffer.
//
import Foundation
/// Renders TViews to terminal output.
/// Renders Views to terminal output.
///
/// The `ViewRenderer` uses a two-pass approach:
/// 1. Render the entire view tree into a `FrameBuffer`
@@ -29,7 +29,7 @@ public final class ViewRenderer {
/// - view: The view to render.
/// - row: The starting row (1-based, default: 1).
/// - column: The starting column (1-based, default: 1).
public func render<V: TView>(_ view: V, atRow row: Int = 1, column: Int = 1) {
public func render<V: View>(_ view: V, atRow row: Int = 1, column: Int = 1) {
let context = RenderContext(terminal: terminal)
let buffer = renderToBuffer(view, context: context)
flush(buffer, atRow: row, column: column)
@@ -68,7 +68,7 @@ protocol ChildInfoProvider {
}
/// Creates a ChildInfo for a single view.
func makeChildInfo<V: TView>(for view: V, context: RenderContext) -> ChildInfo {
func makeChildInfo<V: View>(for view: V, context: RenderContext) -> ChildInfo {
if let spacer = view as? Spacer {
return ChildInfo(buffer: nil, isSpacer: true, spacerMinLength: spacer.minLength)
}
@@ -358,9 +358,9 @@ extension ConditionalView: Renderable {
}
}
// MARK: - TViewArray Rendering
// MARK: - ViewArray Rendering
extension TViewArray: Renderable, ChildInfoProvider {
extension ViewArray: Renderable, ChildInfoProvider {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
FrameBuffer(verticallyStacking: childInfos(context: context).compactMap(\.buffer))
}
@@ -372,7 +372,7 @@ extension TViewArray: Renderable, ChildInfoProvider {
// MARK: - Optional Rendering
extension Optional: Renderable where Wrapped: TView {
extension Optional: Renderable where Wrapped: View {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
switch self {
case .some(let view):
@@ -395,7 +395,7 @@ extension Optional: Renderable where Wrapped: TView {
/// - content: The content view.
/// - context: The rendering context.
/// - Returns: An array of ChildInfo.
func resolveChildInfos<V: TView>(from content: V, context: RenderContext) -> [ChildInfo] {
func resolveChildInfos<V: View>(from content: V, context: RenderContext) -> [ChildInfo] {
if let provider = content as? ChildInfoProvider {
return provider.childInfos(context: context)
}
+2 -2
View File
@@ -13,7 +13,7 @@ public let swiftTUIVersion = "0.1.0"
/// Executes a view closure and renders it once.
///
/// This is useful for simple CLI tools that don't need a full TApp.
/// This is useful for simple CLI tools that don't need a full App.
///
/// # Example
///
@@ -32,7 +32,7 @@ public let swiftTUIVersion = "0.1.0"
///
/// - Parameter content: A ViewBuilder closure that defines the view to render.
@discardableResult
public func renderOnce<Content: TView>(@TViewBuilder content: () -> Content) -> Int {
public func renderOnce<Content: View>(@ViewBuilder content: () -> Content) -> Int {
let view = content()
let renderer = ViewRenderer()
renderer.render(view)
+11 -11
View File
@@ -29,7 +29,7 @@
/// Alert(title: "Notice", message: "Operation complete!")
/// }
/// ```
public struct Alert<Actions: TView>: TView {
public struct Alert<Actions: View>: View {
/// The alert title.
public let title: String
@@ -63,7 +63,7 @@ public struct Alert<Actions: TView>: TView {
borderStyle: BorderStyle = .rounded,
borderColor: Color? = nil,
titleColor: Color? = nil,
@TViewBuilder actions: () -> Actions
@ViewBuilder actions: () -> Actions
) {
self.title = title
self.message = message
@@ -73,7 +73,7 @@ public struct Alert<Actions: TView>: TView {
self.actions = actions()
}
public var body: some TView {
public var body: some View {
VStack(spacing: 1) {
// Title
if let color = titleColor {
@@ -136,10 +136,10 @@ extension Alert {
/// - message: The alert message.
/// - actions: The action views.
/// - Returns: A warning-styled alert.
public static func warning<A: TView>(
public static func warning<A: View>(
title: String = "Warning",
message: String,
@TViewBuilder actions: () -> A
@ViewBuilder actions: () -> A
) -> Alert<A> {
Alert<A>(
title: title,
@@ -158,10 +158,10 @@ extension Alert {
/// - message: The alert message.
/// - actions: The action views.
/// - Returns: An error-styled alert.
public static func error<A: TView>(
public static func error<A: View>(
title: String = "Error",
message: String,
@TViewBuilder actions: () -> A
@ViewBuilder actions: () -> A
) -> Alert<A> {
Alert<A>(
title: title,
@@ -180,10 +180,10 @@ extension Alert {
/// - message: The alert message.
/// - actions: The action views.
/// - Returns: An info-styled alert.
public static func info<A: TView>(
public static func info<A: View>(
title: String = "Info",
message: String,
@TViewBuilder actions: () -> A
@ViewBuilder actions: () -> A
) -> Alert<A> {
Alert<A>(
title: title,
@@ -202,10 +202,10 @@ extension Alert {
/// - message: The alert message.
/// - actions: The action views.
/// - Returns: A success-styled alert.
public static func success<A: TView>(
public static func success<A: View>(
title: String = "Success",
message: String,
@TViewBuilder actions: () -> A
@ViewBuilder actions: () -> A
) -> Alert<A> {
Alert<A>(
title: title,
+3 -3
View File
@@ -24,7 +24,7 @@
/// }
/// }
/// ```
public struct Box<Content: TView>: TView {
public struct Box<Content: View>: View {
/// The content of the box.
public let content: Content
@@ -43,14 +43,14 @@ public struct Box<Content: TView>: TView {
public init(
_ borderStyle: BorderStyle = .line,
color: Color? = nil,
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.content = content()
self.borderStyle = borderStyle
self.borderColor = color
}
public var body: some TView {
public var body: some View {
content.border(borderStyle, color: borderColor)
}
}
+2 -2
View File
@@ -115,7 +115,7 @@ public struct ButtonStyle: Sendable {
/// dismiss()
/// }
/// ```
public struct Button: TView {
public struct Button: View {
/// The button's label text.
public let label: String
@@ -352,7 +352,7 @@ extension Button {
/// Button("OK", style: .primary) { confirm() }
/// }
/// ```
public struct ButtonRow: TView {
public struct ButtonRow: View {
private let buttons: [Button]
private let spacing: Int
+3 -3
View File
@@ -23,7 +23,7 @@
/// Text("Styled Card")
/// }
/// ```
public struct Card<Content: TView>: TView {
public struct Card<Content: View>: View {
/// The content of the card.
public let content: Content
@@ -52,7 +52,7 @@ public struct Card<Content: TView>: TView {
borderColor: Color? = nil,
backgroundColor: Color? = nil,
padding: EdgeInsets = EdgeInsets(all: 1),
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.content = content()
self.borderStyle = borderStyle
@@ -61,7 +61,7 @@ public struct Card<Content: TView>: TView {
self.padding = padding
}
public var body: some TView {
public var body: some View {
// Build the card by composing modifiers
if let bgColor = backgroundColor {
content
+11 -11
View File
@@ -41,7 +41,7 @@
/// }
/// }
/// ```
public struct Dialog<Content: TView>: TView {
public struct Dialog<Content: View>: View {
/// The dialog title.
public let title: String
@@ -75,7 +75,7 @@ public struct Dialog<Content: TView>: TView {
borderColor: Color? = nil,
titleColor: Color? = nil,
padding: EdgeInsets = EdgeInsets(horizontal: 2, vertical: 1),
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.title = title
self.borderStyle = borderStyle
@@ -85,7 +85,7 @@ public struct Dialog<Content: TView>: TView {
self.content = content()
}
public var body: some TView {
public var body: some View {
Panel(
title,
borderStyle: borderStyle,
@@ -109,11 +109,11 @@ extension Dialog {
/// - titleColor: The title color (default: nil).
/// - content: The dialog content.
/// - Returns: A dialog with double-line borders.
public static func doubleLine<C: TView>(
public static func doubleLine<C: View>(
title: String,
borderColor: Color? = nil,
titleColor: Color? = nil,
@TViewBuilder content: () -> C
@ViewBuilder content: () -> C
) -> Dialog<C> {
Dialog<C>(
title: title,
@@ -132,11 +132,11 @@ extension Dialog {
/// - titleColor: The title color (default: nil).
/// - content: The dialog content.
/// - Returns: A dialog with heavy borders.
public static func heavy<C: TView>(
public static func heavy<C: View>(
title: String,
borderColor: Color? = nil,
titleColor: Color? = nil,
@TViewBuilder content: () -> C
@ViewBuilder content: () -> C
) -> Dialog<C> {
Dialog<C>(
title: title,
@@ -150,7 +150,7 @@ extension Dialog {
// MARK: - Modal Presentation Helper
extension TView {
extension View {
/// Presents this view as a modal dialog over dimmed content.
///
/// This is a convenience method that combines `.dimmed()` and `.overlay()`
@@ -169,9 +169,9 @@ extension TView {
///
/// - Parameter content: The modal content to display.
/// - Returns: A view with the modal overlay.
public func modal<Modal: TView>(
@TViewBuilder content: () -> Modal
) -> some TView {
public func modal<Modal: View>(
@ViewBuilder content: () -> Modal
) -> some View {
self.dimmed()
.overlay(alignment: .center, content: content)
}
+4 -4
View File
@@ -39,7 +39,7 @@
/// }
/// }
/// ```
public struct ForEach<Data: RandomAccessCollection, ID: Hashable, Content: TView>: TView {
public struct ForEach<Data: RandomAccessCollection, ID: Hashable, Content: View>: View {
/// The underlying data collection.
public let data: Data
@@ -58,7 +58,7 @@ public struct ForEach<Data: RandomAccessCollection, ID: Hashable, Content: TView
public init(
_ data: Data,
id: KeyPath<Data.Element, ID>,
@TViewBuilder content: @escaping (Data.Element) -> Content
@ViewBuilder content: @escaping (Data.Element) -> Content
) {
self.data = data
self.idKeyPath = id
@@ -80,7 +80,7 @@ extension ForEach where Data.Element: Identifiable, ID == Data.Element.ID {
/// - content: The closure that creates the view for each element.
public init(
_ data: Data,
@TViewBuilder content: @escaping (Data.Element) -> Content
@ViewBuilder content: @escaping (Data.Element) -> Content
) {
self.data = data
self.idKeyPath = \Data.Element.id
@@ -98,7 +98,7 @@ extension ForEach where Data == Range<Int>, ID == Int {
/// - content: The closure that creates the view for each index.
public init(
_ data: Range<Int>,
@TViewBuilder content: @escaping (Int) -> Content
@ViewBuilder content: @escaping (Int) -> Content
) {
self.data = data
self.idKeyPath = \.self
+7 -7
View File
@@ -51,10 +51,10 @@ public struct MenuItem: Identifiable {
/// # Interactive Example (with Binding)
///
/// ```swift
/// struct ContentView: TView {
/// @TState var selection = 0
/// struct ContentView: View {
/// @State var selection = 0
///
/// var body: some TView {
/// var body: some View {
/// Menu(
/// title: "Main Menu",
/// items: menuItems,
@@ -66,7 +66,7 @@ public struct MenuItem: Identifiable {
/// }
/// }
/// ```
public struct Menu: TView {
public struct Menu: View {
/// The menu title (optional).
public let title: String?
@@ -347,11 +347,11 @@ extension Menu: Renderable {
///
/// This is a temporary solution until we have proper `@ViewBuilder`
/// support for complex conditionals.
public struct AnyView: TView {
public struct AnyView: View {
private let _render: (RenderContext) -> FrameBuffer
/// Creates an AnyView wrapping the given view.
public init<V: TView>(_ view: V) {
public init<V: View>(_ view: V) {
self._render = { context in
SwiftTUI.renderToBuffer(view, context: context)
}
@@ -368,7 +368,7 @@ extension AnyView: Renderable {
}
}
extension TView {
extension View {
/// Wraps this view in an AnyView for type erasure.
///
/// Use this when you need to return different view types from
+2 -2
View File
@@ -23,7 +23,7 @@
/// Text("Age: 30")
/// }
/// ```
public struct Panel<Content: TView>: TView {
public struct Panel<Content: View>: View {
/// The title displayed in the top border.
public let title: String
@@ -57,7 +57,7 @@ public struct Panel<Content: TView>: TView {
borderColor: Color? = nil,
titleColor: Color? = nil,
padding: EdgeInsets = EdgeInsets(horizontal: 1, vertical: 0),
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.title = title
self.content = content()
+2 -2
View File
@@ -30,7 +30,7 @@
/// Text("Bottom")
/// }
/// ```
public struct Spacer: TView {
public struct Spacer: View {
/// The minimum length of the spacer (in characters/lines).
public let minLength: Int?
@@ -67,7 +67,7 @@ public struct Spacer: TView {
/// // ─────────────
/// // Section 2
/// ```
public struct Divider: TView {
public struct Divider: View {
/// The character used for the line.
public var character: Character
+6 -6
View File
@@ -30,7 +30,7 @@
/// Text("Longer text")
/// }
/// ```
public struct VStack<Content: TView>: TView {
public struct VStack<Content: View>: View {
/// The horizontal alignment of the children.
public let alignment: HorizontalAlignment
@@ -49,7 +49,7 @@ public struct VStack<Content: TView>: TView {
public init(
alignment: HorizontalAlignment = .leading,
spacing: Int = 0,
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.alignment = alignment
self.spacing = spacing
@@ -84,7 +84,7 @@ public struct VStack<Content: TView>: TView {
/// Text("Right")
/// }
/// ```
public struct HStack<Content: TView>: TView {
public struct HStack<Content: View>: View {
/// The vertical alignment of the children.
public let alignment: VerticalAlignment
@@ -103,7 +103,7 @@ public struct HStack<Content: TView>: TView {
public init(
alignment: VerticalAlignment = .center,
spacing: Int = 1,
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.alignment = alignment
self.spacing = spacing
@@ -130,7 +130,7 @@ public struct HStack<Content: TView>: TView {
/// Text(" Overlay ")
/// }
/// ```
public struct ZStack<Content: TView>: TView {
public struct ZStack<Content: View>: View {
/// The alignment of the children.
public let alignment: Alignment
@@ -144,7 +144,7 @@ public struct ZStack<Content: TView>: TView {
/// - content: A ViewBuilder that defines the children.
public init(
alignment: Alignment = .center,
@TViewBuilder content: () -> Content
@ViewBuilder content: () -> Content
) {
self.alignment = alignment
self.content = content()
+36 -36
View File
@@ -11,7 +11,7 @@ import Foundation
// MARK: - Status Bar Style
/// The visual style of the status bar.
public enum TStatusBarStyle: Sendable {
public enum StatusBarStyle: Sendable {
/// A single line with horizontal padding.
case compact
@@ -22,7 +22,7 @@ public enum TStatusBarStyle: Sendable {
// MARK: - Status Bar Alignment
/// The horizontal alignment of items within the status bar.
public enum TStatusBarAlignment: Sendable {
public enum StatusBarAlignment: Sendable {
/// Items are aligned to the left (leading edge).
case leading
@@ -46,9 +46,9 @@ public enum TStatusBarAlignment: Sendable {
/// # Example
///
/// ```swift
/// TStatusBarItem(shortcut: .escape, label: "close") { dismiss() }
/// TStatusBarItem(shortcut: .arrowsUpDown, label: "nav")
/// TStatusBarItem(shortcut: .enter, label: "select", key: .enter)
/// StatusBarItem(shortcut: .escape, label: "close") { dismiss() }
/// StatusBarItem(shortcut: .arrowsUpDown, label: "nav")
/// StatusBarItem(shortcut: .enter, label: "select", key: .enter)
/// ```
public enum Shortcut {
// MARK: - Special Keys
@@ -241,8 +241,8 @@ public enum Shortcut {
/// A protocol for items that can be displayed in a status bar.
///
/// Implement this protocol to create custom status bar items.
/// The default `TStatusBarItem` already conforms to this protocol.
public protocol TStatusBarItemProtocol: Sendable {
/// The default `StatusBarItem` already conforms to this protocol.
public protocol StatusBarItemProtocol: Sendable {
/// The unique identifier for this item.
var id: String { get }
@@ -264,7 +264,7 @@ public protocol TStatusBarItemProtocol: Sendable {
}
// Default implementation for triggerKey matching
public extension TStatusBarItemProtocol {
public extension StatusBarItemProtocol {
func matches(_ event: KeyEvent) -> Bool {
guard let trigger = triggerKey else { return false }
return event.key == trigger
@@ -278,13 +278,13 @@ public extension TStatusBarItemProtocol {
/// # Example
///
/// ```swift
/// TStatusBarItem(shortcut: "q", label: "quit") {
/// StatusBarItem(shortcut: "q", label: "quit") {
/// app.quit()
/// }
///
/// TStatusBarItem(shortcut: "↑↓", label: "nav", key: .up) // Info only, no action
/// StatusBarItem(shortcut: "↑↓", label: "nav", key: .up) // Info only, no action
/// ```
public struct TStatusBarItem: TStatusBarItemProtocol, Identifiable {
public struct StatusBarItem: StatusBarItemProtocol, Identifiable {
public let id: String
public let shortcut: String
public let label: String
@@ -395,32 +395,32 @@ public struct TStatusBarItem: TStatusBarItemProtocol, Identifiable {
/// Result builder for creating status bar items.
@resultBuilder
public struct StatusBarItemBuilder {
public static func buildBlock(_ components: [any TStatusBarItemProtocol]...) -> [any TStatusBarItemProtocol] {
public static func buildBlock(_ components: [any StatusBarItemProtocol]...) -> [any StatusBarItemProtocol] {
components.flatMap { $0 }
}
public static func buildArray(_ components: [[any TStatusBarItemProtocol]]) -> [any TStatusBarItemProtocol] {
public static func buildArray(_ components: [[any StatusBarItemProtocol]]) -> [any StatusBarItemProtocol] {
components.flatMap { $0 }
}
public static func buildOptional(_ component: [any TStatusBarItemProtocol]?) -> [any TStatusBarItemProtocol] {
public static func buildOptional(_ component: [any StatusBarItemProtocol]?) -> [any StatusBarItemProtocol] {
component ?? []
}
public static func buildEither(first component: [any TStatusBarItemProtocol]) -> [any TStatusBarItemProtocol] {
public static func buildEither(first component: [any StatusBarItemProtocol]) -> [any StatusBarItemProtocol] {
component
}
public static func buildEither(second component: [any TStatusBarItemProtocol]) -> [any TStatusBarItemProtocol] {
public static func buildEither(second component: [any StatusBarItemProtocol]) -> [any StatusBarItemProtocol] {
component
}
public static func buildExpression(_ expression: any TStatusBarItemProtocol) -> [any TStatusBarItemProtocol] {
public static func buildExpression(_ expression: any StatusBarItemProtocol) -> [any StatusBarItemProtocol] {
[expression]
}
}
// MARK: - TStatusBar View
// MARK: - StatusBar View
/// A status bar that displays at the bottom of the terminal.
///
@@ -433,31 +433,31 @@ public struct StatusBarItemBuilder {
/// To set status bar items, use the environment:
///
/// ```swift
/// struct MyView: TView {
/// struct MyView: View {
/// @Environment(\.statusBar) var statusBar
///
/// var body: some TView {
/// var body: some View {
/// VStack {
/// Text("Hello")
/// }
/// .onAppear {
/// statusBar.setItems([
/// TStatusBarItem(shortcut: "q", label: "quit"),
/// TStatusBarItem(shortcut: "↑↓", label: "nav"),
/// StatusBarItem(shortcut: "q", label: "quit"),
/// StatusBarItem(shortcut: "↑↓", label: "nav"),
/// ])
/// }
/// }
/// }
/// ```
public struct TStatusBar: TView {
public struct StatusBar: View {
/// The items to display.
public let items: [any TStatusBarItemProtocol]
public let items: [any StatusBarItemProtocol]
/// The visual style.
public let style: TStatusBarStyle
public let style: StatusBarStyle
/// The horizontal alignment of items.
public let alignment: TStatusBarAlignment
public let alignment: StatusBarAlignment
/// The highlight color for shortcut keys.
public let highlightColor: Color
@@ -474,9 +474,9 @@ public struct TStatusBar: TView {
/// - highlightColor: The color for shortcut keys (default: `.cyan`).
/// - labelColor: The color for labels (default: nil, terminal default).
public init(
items: [any TStatusBarItemProtocol],
style: TStatusBarStyle = .compact,
alignment: TStatusBarAlignment = .justified,
items: [any StatusBarItemProtocol],
style: StatusBarStyle = .compact,
alignment: StatusBarAlignment = .justified,
highlightColor: Color = .cyan,
labelColor: Color? = nil
) {
@@ -496,11 +496,11 @@ public struct TStatusBar: TView {
/// - labelColor: The color for labels.
/// - builder: A closure that returns items.
public init(
style: TStatusBarStyle = .compact,
alignment: TStatusBarAlignment = .justified,
style: StatusBarStyle = .compact,
alignment: StatusBarAlignment = .justified,
highlightColor: Color = .cyan,
labelColor: Color? = nil,
@StatusBarItemBuilder _ builder: () -> [any TStatusBarItemProtocol]
@StatusBarItemBuilder _ builder: () -> [any StatusBarItemProtocol]
) {
self.items = builder()
self.style = style
@@ -510,13 +510,13 @@ public struct TStatusBar: TView {
}
public var body: Never {
fatalError("TStatusBar renders via Renderable")
fatalError("StatusBar renders via Renderable")
}
}
// MARK: - TStatusBar Rendering
// MARK: - StatusBar Rendering
extension TStatusBar: Renderable {
extension StatusBar: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
guard !items.isEmpty else {
return FrameBuffer()
@@ -683,7 +683,7 @@ extension TStatusBar: Renderable {
// MARK: - Status Bar Height Helper
extension TStatusBar {
extension StatusBar {
/// The height of the status bar in lines.
public var height: Int {
switch style {
+1 -1
View File
@@ -21,7 +21,7 @@
/// Text("Colored")
/// .foregroundColor(.red)
/// ```
public struct Text: TView {
public struct Text: View {
/// The text to display.
public let content: String
@@ -19,16 +19,16 @@ import SwiftTUI
/// Text("Feature 2")
/// }
/// ```
struct DemoSection<Content: TView>: TView {
struct DemoSection<Content: View>: View {
let title: String
let content: Content
init(_ title: String, @TViewBuilder content: () -> Content) {
init(_ title: String, @ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some TView {
var body: some View {
VStack(alignment: .leading) {
Text(title)
.bold()
@@ -20,7 +20,7 @@ import SwiftTUI
/// subtitle: "An optional description"
/// )
/// ```
struct HeaderView: TView {
struct HeaderView: View {
let title: String
let subtitle: String?
@@ -29,7 +29,7 @@ struct HeaderView: TView {
self.subtitle = subtitle
}
var body: some TView {
var body: some View {
VStack {
HStack {
Text(title)
+12 -12
View File
@@ -14,8 +14,8 @@ import SwiftTUI
/// This view acts as a router, displaying the appropriate demo page
/// based on the current state. It uses the `.statusBarItems()` modifier
/// to declaratively set context-sensitive status bar items.
struct ContentView: TView {
var body: some TView {
struct ContentView: View {
var body: some View {
let state = ExampleAppState.shared
// Show current page based on state
@@ -36,16 +36,16 @@ struct ContentView: TView {
}
}
@TViewBuilder
private func pageContent(for page: DemoPage) -> some TView {
@ViewBuilder
private func pageContent(for page: DemoPage) -> some View {
switch page {
case .menu:
MainMenuPage()
.statusBarItems {
TStatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "nav")
TStatusBarItem(shortcut: Shortcut.enter, label: "select", key: .enter)
TStatusBarItem(shortcut: Shortcut.range("1", "6"), label: "jump")
TStatusBarItem(shortcut: Shortcut.quit, label: "quit")
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "nav")
StatusBarItem(shortcut: Shortcut.enter, label: "select", key: .enter)
StatusBarItem(shortcut: Shortcut.range("1", "6"), label: "jump")
StatusBarItem(shortcut: Shortcut.quit, label: "quit")
}
case .textStyles:
TextStylesPage()
@@ -69,13 +69,13 @@ struct ContentView: TView {
}
/// Common status bar items for sub-pages.
private var subPageItems: [any TStatusBarItemProtocol] {
private var subPageItems: [any StatusBarItemProtocol] {
[
TStatusBarItem(shortcut: Shortcut.escape, label: "back") {
StatusBarItem(shortcut: Shortcut.escape, label: "back") {
ExampleAppState.shared.currentPage = .menu
},
TStatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"),
TStatusBarItem(shortcut: Shortcut.quit, label: "quit")
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"),
StatusBarItem(shortcut: Shortcut.quit, label: "quit")
]
}
}
@@ -15,8 +15,8 @@ import SwiftTUI
/// - Plain style (no border)
/// - ButtonRow for horizontal groups
/// - Focus navigation with Tab
struct ButtonsPage: TView {
var body: some TView {
struct ButtonsPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Buttons & Focus Demo")
@@ -14,8 +14,8 @@ import SwiftTUI
/// - Bright colors (8 colors)
/// - RGB colors (24-bit true color)
/// - Semantic colors (primary, success, warning, error)
struct ColorsPage: TView {
var body: some TView {
struct ColorsPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Colors Demo")
@@ -14,8 +14,8 @@ import SwiftTUI
/// - Box (simple bordered container)
/// - Panel (container with title in border)
/// - All available border styles
struct ContainersPage: TView {
var body: some TView {
struct ContainersPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Container Views Demo")
@@ -14,8 +14,8 @@ import SwiftTUI
/// - HStack (horizontal stacking)
/// - Spacer (flexible space)
/// - Padding and frame modifiers
struct LayoutPage: TView {
var body: some TView {
struct LayoutPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Layout System Demo")
@@ -11,8 +11,8 @@ import SwiftTUI
///
/// Displays a centered menu with all available demos and
/// feature highlight boxes at the bottom.
struct MainMenuPage: TView {
var body: some TView {
struct MainMenuPage: View {
var body: some View {
let state = ExampleAppState.shared
VStack(spacing: 1) {
@@ -67,7 +67,7 @@ struct MainMenuPage: TView {
}
/// Creates a small feature highlight box.
private func featureBox(_ title: String, _ subtitle: String) -> some TView {
private func featureBox(_ title: String, _ subtitle: String) -> some View {
VStack {
Text(title)
.bold()
@@ -14,8 +14,8 @@ import SwiftTUI
/// - `.dimmed()` modifier
/// - `.modal()` helper
/// - Note: The status bar is NOT dimmed by modals!
struct OverlaysPage: TView {
var body: some TView {
struct OverlaysPage: View {
var body: some View {
// Background content with modal overlay
backgroundContent
.modal {
@@ -34,7 +34,7 @@ struct OverlaysPage: TView {
}
}
var backgroundContent: some TView {
var backgroundContent: some View {
VStack(spacing: 1) {
HeaderView(title: "Overlays & Modals Demo")
@@ -13,8 +13,8 @@ import SwiftTUI
/// - Basic styles (bold, italic, underline, etc.)
/// - Combined styles
/// - Special effects (blink, inverted)
struct TextStylesPage: TView {
var body: some TView {
struct TextStylesPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Text Styles Demo")
+2 -2
View File
@@ -13,8 +13,8 @@ import SwiftTUI
// MARK: - Main App
/// The main example application.
struct ExampleApp: TApp {
var body: some TScene {
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
+2 -2
View File
@@ -93,8 +93,8 @@ struct RenderingTests {
@Test("Composite view renders through body")
func compositeView() {
struct MyView: TView {
var body: some TView {
struct MyView: View {
var body: some View {
VStack {
Text("Hello")
Text("World")
+128 -128
View File
@@ -2,7 +2,7 @@
// StatusBarTests.swift
// SwiftTUI
//
// Tests for Shortcut constants, TStatusBarItem, StatusBarManager, and TStatusBar.
// Tests for Shortcut constants, StatusBarItem, StatusBarManager, and StatusBar.
//
import Testing
@@ -120,16 +120,16 @@ struct ShortcutTests {
@Suite("Status Bar Item Tests")
struct StatusBarItemTests {
@Test("TStatusBarItem can be created")
@Test("StatusBarItem can be created")
func itemCreation() {
let item = TStatusBarItem(shortcut: "q", label: "quit")
let item = StatusBarItem(shortcut: "q", label: "quit")
#expect(item.shortcut == "q")
#expect(item.label == "quit")
#expect(item.id == "q-quit")
}
@Test("TStatusBarItem with action")
@Test("StatusBarItem with action")
func itemWithAction() {
// Use a class to track execution since the closure is @Sendable
final class ExecutionTracker: @unchecked Sendable {
@@ -137,7 +137,7 @@ struct StatusBarItemTests {
}
let tracker = ExecutionTracker()
let item = TStatusBarItem(shortcut: "x", label: "execute") {
let item = StatusBarItem(shortcut: "x", label: "execute") {
tracker.wasExecuted = true
}
@@ -145,30 +145,30 @@ struct StatusBarItemTests {
#expect(tracker.wasExecuted == true)
}
@Test("TStatusBarItem derives key from single character")
@Test("StatusBarItem derives key from single character")
func deriveKeyFromCharacter() {
let item = TStatusBarItem(shortcut: "q", label: "quit")
let item = StatusBarItem(shortcut: "q", label: "quit")
#expect(item.triggerKey == .character("q"))
}
@Test("TStatusBarItem derives key from escape symbol")
@Test("StatusBarItem derives key from escape symbol")
func deriveKeyFromEscape() {
let item = TStatusBarItem(shortcut: Shortcut.escape, label: "close")
let item = StatusBarItem(shortcut: Shortcut.escape, label: "close")
#expect(item.triggerKey == .escape)
}
@Test("TStatusBarItem derives key from enter symbol")
@Test("StatusBarItem derives key from enter symbol")
func deriveKeyFromEnter() {
let item = TStatusBarItem(shortcut: Shortcut.enter, label: "confirm")
let item = StatusBarItem(shortcut: Shortcut.enter, label: "confirm")
#expect(item.triggerKey == .enter)
}
@Test("TStatusBarItem with explicit key")
@Test("StatusBarItem with explicit key")
func itemWithExplicitKey() {
let item = TStatusBarItem(
let item = StatusBarItem(
shortcut: "navigate",
label: "nav",
key: .up
@@ -177,19 +177,19 @@ struct StatusBarItemTests {
#expect(item.triggerKey == .up)
}
@Test("TStatusBarItem informational has no trigger key")
@Test("StatusBarItem informational has no trigger key")
func informationalItem() {
// Multi-character shortcut without explicit key
let item = TStatusBarItem(shortcut: "↑↓", label: "nav")
let item = StatusBarItem(shortcut: "↑↓", label: "nav")
// Arrow combinations don't have a single trigger key
// but matches() handles them specially
#expect(item.triggerKey == nil)
}
@Test("TStatusBarItem matches character key")
@Test("StatusBarItem matches character key")
func matchesCharacterKey() {
let item = TStatusBarItem(shortcut: "q", label: "quit")
let item = StatusBarItem(shortcut: "q", label: "quit")
let event = KeyEvent(key: .character("q"))
#expect(item.matches(event) == true)
@@ -198,10 +198,10 @@ struct StatusBarItemTests {
#expect(item.matches(wrongEvent) == false)
}
@Test("TStatusBarItem case sensitive matching")
@Test("StatusBarItem case sensitive matching")
func caseSensitiveMatching() {
let lowerItem = TStatusBarItem(shortcut: "n", label: "new")
let upperItem = TStatusBarItem(shortcut: "N", label: "New")
let lowerItem = StatusBarItem(shortcut: "n", label: "new")
let upperItem = StatusBarItem(shortcut: "N", label: "New")
let lowerEvent = KeyEvent(key: .character("n"))
let upperEvent = KeyEvent(key: .character("N"))
@@ -213,9 +213,9 @@ struct StatusBarItemTests {
#expect(upperItem.matches(lowerEvent) == false)
}
@Test("TStatusBarItem matches arrow combinations")
@Test("StatusBarItem matches arrow combinations")
func matchesArrowCombinations() {
let item = TStatusBarItem(shortcut: "↑↓", label: "nav")
let item = StatusBarItem(shortcut: "↑↓", label: "nav")
let upEvent = KeyEvent(key: .up)
let downEvent = KeyEvent(key: .down)
@@ -226,9 +226,9 @@ struct StatusBarItemTests {
#expect(item.matches(leftEvent) == false)
}
@Test("TStatusBarItem matches all arrows")
@Test("StatusBarItem matches all arrows")
func matchesAllArrows() {
let item = TStatusBarItem(shortcut: Shortcut.arrowsAll, label: "move")
let item = StatusBarItem(shortcut: Shortcut.arrowsAll, label: "move")
#expect(item.matches(KeyEvent(key: .up)) == true)
#expect(item.matches(KeyEvent(key: .down)) == true)
@@ -254,8 +254,8 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "q", label: "quit"),
TStatusBarItem(shortcut: "h", label: "help")
StatusBarItem(shortcut: "q", label: "quit"),
StatusBarItem(shortcut: "h", label: "help")
])
#expect(state.currentItems.count == 2)
@@ -267,8 +267,8 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems {
TStatusBarItem(shortcut: "q", label: "quit")
TStatusBarItem(shortcut: "h", label: "help")
StatusBarItem(shortcut: "q", label: "quit")
StatusBarItem(shortcut: "h", label: "help")
}
#expect(state.currentItems.count == 2)
@@ -279,12 +279,12 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "q", label: "quit")
StatusBarItem(shortcut: "q", label: "quit")
])
state.push(context: "dialog", items: [
TStatusBarItem(shortcut: Shortcut.escape, label: "close"),
TStatusBarItem(shortcut: Shortcut.enter, label: "confirm")
StatusBarItem(shortcut: Shortcut.escape, label: "close"),
StatusBarItem(shortcut: Shortcut.enter, label: "confirm")
])
#expect(state.currentItems.count == 2)
@@ -296,7 +296,7 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.push(context: "test") {
TStatusBarItem(shortcut: "a", label: "action")
StatusBarItem(shortcut: "a", label: "action")
}
#expect(state.currentItems.count == 1)
@@ -308,11 +308,11 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "g", label: "global")
StatusBarItem(shortcut: "g", label: "global")
])
state.push(context: "temp", items: [
TStatusBarItem(shortcut: "t", label: "temp")
StatusBarItem(shortcut: "t", label: "temp")
])
state.pop(context: "temp")
@@ -326,11 +326,11 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.push(context: "first", items: [
TStatusBarItem(shortcut: "1", label: "first")
StatusBarItem(shortcut: "1", label: "first")
])
state.push(context: "second", items: [
TStatusBarItem(shortcut: "2", label: "second")
StatusBarItem(shortcut: "2", label: "second")
])
// Top of stack is shown
@@ -345,11 +345,11 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.push(context: "same", items: [
TStatusBarItem(shortcut: "a", label: "original")
StatusBarItem(shortcut: "a", label: "original")
])
state.push(context: "same", items: [
TStatusBarItem(shortcut: "b", label: "replaced")
StatusBarItem(shortcut: "b", label: "replaced")
])
#expect(state.currentItems.count == 1)
@@ -361,11 +361,11 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "g", label: "global")
StatusBarItem(shortcut: "g", label: "global")
])
state.push(context: "ctx", items: [
TStatusBarItem(shortcut: "c", label: "context")
StatusBarItem(shortcut: "c", label: "context")
])
state.clearContexts()
@@ -379,11 +379,11 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "g", label: "global")
StatusBarItem(shortcut: "g", label: "global")
])
state.push(context: "ctx", items: [
TStatusBarItem(shortcut: "c", label: "context")
StatusBarItem(shortcut: "c", label: "context")
])
state.clear()
@@ -403,7 +403,7 @@ struct StatusBarStateTests {
let tracker = TriggerTracker()
state.setItems([
TStatusBarItem(shortcut: "t", label: "trigger") {
StatusBarItem(shortcut: "t", label: "trigger") {
tracker.wasTriggered = true
}
])
@@ -420,7 +420,7 @@ struct StatusBarStateTests {
let state = StatusBarState()
state.setItems([
TStatusBarItem(shortcut: "a", label: "action") {}
StatusBarItem(shortcut: "a", label: "action") {}
])
let event = KeyEvent(key: .character("x"))
@@ -478,7 +478,7 @@ struct StatusBarStateTests {
func heightCompact() {
let state = StatusBarState()
state.style = .compact
state.setItems([TStatusBarItem(shortcut: "x", label: "test")])
state.setItems([StatusBarItem(shortcut: "x", label: "test")])
#expect(state.height == 1)
}
@@ -486,21 +486,21 @@ struct StatusBarStateTests {
func heightBordered() {
let state = StatusBarState()
state.style = .bordered
state.setItems([TStatusBarItem(shortcut: "x", label: "test")])
state.setItems([StatusBarItem(shortcut: "x", label: "test")])
#expect(state.height == 3)
}
}
// MARK: - TStatusBar Tests
// MARK: - StatusBar Tests
@Suite("TStatusBar Tests")
struct TStatusBarTests {
@Suite("StatusBar Tests")
struct StatusBarTests {
@Test("TStatusBar can be created with items")
@Test("StatusBar can be created with items")
func statusBarCreation() {
let statusBar = TStatusBar(items: [
TStatusBarItem(shortcut: "q", label: "quit"),
TStatusBarItem(shortcut: "h", label: "help")
let statusBar = StatusBar(items: [
StatusBarItem(shortcut: "q", label: "quit"),
StatusBarItem(shortcut: "h", label: "help")
])
#expect(statusBar.items.count == 2)
@@ -509,19 +509,19 @@ struct TStatusBarTests {
#expect(statusBar.highlightColor == .cyan)
}
@Test("TStatusBar with style")
@Test("StatusBar with style")
func statusBarWithStyle() {
let statusBar = TStatusBar(
items: [TStatusBarItem(shortcut: "x", label: "test")],
let statusBar = StatusBar(
items: [StatusBarItem(shortcut: "x", label: "test")],
style: .bordered
)
#expect(statusBar.style == .bordered)
}
@Test("TStatusBar with custom colors")
@Test("StatusBar with custom colors")
func statusBarWithColors() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [],
highlightColor: .yellow,
labelColor: .green
@@ -531,32 +531,32 @@ struct TStatusBarTests {
#expect(statusBar.labelColor == .green)
}
@Test("TStatusBar with builder")
@Test("StatusBar with builder")
func statusBarWithBuilder() {
let statusBar = TStatusBar {
TStatusBarItem(shortcut: "a", label: "alpha")
TStatusBarItem(shortcut: "b", label: "beta")
let statusBar = StatusBar {
StatusBarItem(shortcut: "a", label: "alpha")
StatusBarItem(shortcut: "b", label: "beta")
}
#expect(statusBar.items.count == 2)
}
@Test("TStatusBar compact height")
@Test("StatusBar compact height")
func compactHeight() {
let statusBar = TStatusBar(items: [], style: .compact)
let statusBar = StatusBar(items: [], style: .compact)
#expect(statusBar.height == 1)
}
@Test("TStatusBar bordered height")
@Test("StatusBar bordered height")
func borderedHeight() {
let statusBar = TStatusBar(items: [], style: .bordered)
let statusBar = StatusBar(items: [], style: .bordered)
#expect(statusBar.height == 3)
}
@Test("TStatusBar renders compact style")
@Test("StatusBar renders compact style")
func rendersCompact() {
let statusBar = TStatusBar(items: [
TStatusBarItem(shortcut: "q", label: "quit")
let statusBar = StatusBar(items: [
StatusBarItem(shortcut: "q", label: "quit")
], style: .compact)
let context = RenderContext(availableWidth: 80, availableHeight: 24)
@@ -568,10 +568,10 @@ struct TStatusBarTests {
#expect(content.contains("quit"))
}
@Test("TStatusBar renders bordered style")
@Test("StatusBar renders bordered style")
func rendersBordered() {
let statusBar = TStatusBar(items: [
TStatusBarItem(shortcut: "h", label: "help")
let statusBar = StatusBar(items: [
StatusBarItem(shortcut: "h", label: "help")
], style: .bordered)
let context = RenderContext(availableWidth: 80, availableHeight: 24)
@@ -583,9 +583,9 @@ struct TStatusBarTests {
#expect(allContent.contains("▄") || allContent.contains("█") || allContent.contains("▀"))
}
@Test("Empty TStatusBar returns empty buffer")
@Test("Empty StatusBar returns empty buffer")
func emptyStatusBar() {
let statusBar = TStatusBar(items: [])
let statusBar = StatusBar(items: [])
let context = RenderContext(availableWidth: 80, availableHeight: 24)
let buffer = renderToBuffer(statusBar, context: context)
@@ -593,11 +593,11 @@ struct TStatusBarTests {
#expect(buffer.isEmpty)
}
@Test("TStatusBar renders multiple items with separator")
@Test("StatusBar renders multiple items with separator")
func multipleItemsWithSeparator() {
let statusBar = TStatusBar(items: [
TStatusBarItem(shortcut: "a", label: "alpha"),
TStatusBarItem(shortcut: "b", label: "beta")
let statusBar = StatusBar(items: [
StatusBarItem(shortcut: "a", label: "alpha"),
StatusBarItem(shortcut: "b", label: "beta")
])
let context = RenderContext(availableWidth: 80, availableHeight: 24)
@@ -608,21 +608,21 @@ struct TStatusBarTests {
#expect(content.contains("beta"))
}
@Test("TStatusBar default alignment is justified")
@Test("StatusBar default alignment is justified")
func defaultAlignmentIsJustified() {
let statusBar = TStatusBar(items: [
TStatusBarItem(shortcut: "q", label: "quit")
let statusBar = StatusBar(items: [
StatusBarItem(shortcut: "q", label: "quit")
])
#expect(statusBar.alignment == .justified)
}
@Test("TStatusBar with leading alignment")
@Test("StatusBar with leading alignment")
func leadingAlignment() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [
TStatusBarItem(shortcut: "a", label: "alpha"),
TStatusBarItem(shortcut: "b", label: "beta")
StatusBarItem(shortcut: "a", label: "alpha"),
StatusBarItem(shortcut: "b", label: "beta")
],
alignment: .leading
)
@@ -638,12 +638,12 @@ struct TStatusBarTests {
#expect(!strippedLine.isEmpty)
}
@Test("TStatusBar with trailing alignment")
@Test("StatusBar with trailing alignment")
func trailingAlignment() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [
TStatusBarItem(shortcut: "a", label: "alpha"),
TStatusBarItem(shortcut: "b", label: "beta")
StatusBarItem(shortcut: "a", label: "alpha"),
StatusBarItem(shortcut: "b", label: "beta")
],
alignment: .trailing
)
@@ -657,12 +657,12 @@ struct TStatusBarTests {
#expect(!buffer.isEmpty)
}
@Test("TStatusBar with center alignment")
@Test("StatusBar with center alignment")
func centerAlignment() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [
TStatusBarItem(shortcut: "a", label: "alpha"),
TStatusBarItem(shortcut: "b", label: "beta")
StatusBarItem(shortcut: "a", label: "alpha"),
StatusBarItem(shortcut: "b", label: "beta")
],
alignment: .center
)
@@ -676,13 +676,13 @@ struct TStatusBarTests {
#expect(!buffer.isEmpty)
}
@Test("TStatusBar with justified alignment distributes items")
@Test("StatusBar with justified alignment distributes items")
func justifiedAlignment() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [
TStatusBarItem(shortcut: "a", label: "first"),
TStatusBarItem(shortcut: "b", label: "second"),
TStatusBarItem(shortcut: "c", label: "third")
StatusBarItem(shortcut: "a", label: "first"),
StatusBarItem(shortcut: "b", label: "second"),
StatusBarItem(shortcut: "c", label: "third")
],
alignment: .justified
)
@@ -699,12 +699,12 @@ struct TStatusBarTests {
#expect(content.contains("third"))
}
@Test("TStatusBar bordered with alignment")
@Test("StatusBar bordered with alignment")
func borderedWithAlignment() {
let statusBar = TStatusBar(
let statusBar = StatusBar(
items: [
TStatusBarItem(shortcut: "a", label: "alpha"),
TStatusBarItem(shortcut: "b", label: "beta")
StatusBarItem(shortcut: "a", label: "alpha"),
StatusBarItem(shortcut: "b", label: "beta")
],
style: .bordered,
alignment: .center
@@ -725,12 +725,12 @@ struct TStatusBarTests {
@Suite("Status Bar Alignment Tests")
struct StatusBarAlignmentTests {
@Test("TStatusBarAlignment enum values exist")
@Test("StatusBarAlignment enum values exist")
func alignmentEnumValues() {
let leading: TStatusBarAlignment = .leading
let trailing: TStatusBarAlignment = .trailing
let center: TStatusBarAlignment = .center
let justified: TStatusBarAlignment = .justified
let leading: StatusBarAlignment = .leading
let trailing: StatusBarAlignment = .trailing
let center: StatusBarAlignment = .center
let justified: StatusBarAlignment = .justified
#expect(leading != trailing)
#expect(center != justified)
@@ -738,8 +738,8 @@ struct StatusBarAlignmentTests {
@Test("Single item with justified alignment is centered")
func singleItemJustified() {
let statusBar = TStatusBar(
items: [TStatusBarItem(shortcut: "x", label: "only")],
let statusBar = StatusBar(
items: [StatusBarItem(shortcut: "x", label: "only")],
alignment: .justified
)
@@ -761,8 +761,8 @@ struct StatusBarItemBuilderTests {
@Test("Builder buildBlock combines arrays")
func builderCreatesArray() {
let items = StatusBarItemBuilder.buildBlock(
[TStatusBarItem(shortcut: "a", label: "a")],
[TStatusBarItem(shortcut: "b", label: "b")]
[StatusBarItem(shortcut: "a", label: "a")],
[StatusBarItem(shortcut: "b", label: "b")]
)
#expect(items.count == 2)
@@ -770,17 +770,17 @@ struct StatusBarItemBuilderTests {
@Test("Builder handles expression")
func builderHandlesExpression() {
let item = TStatusBarItem(shortcut: "e", label: "expr")
let item = StatusBarItem(shortcut: "e", label: "expr")
let result = StatusBarItemBuilder.buildExpression(item)
#expect(result.count == 1)
}
@Test("Builder works with TStatusBar initializer")
@Test("Builder works with StatusBar initializer")
func builderWorksWithStatusBar() {
let statusBar = TStatusBar {
TStatusBarItem(shortcut: "x", label: "test")
TStatusBarItem(shortcut: "y", label: "test2")
let statusBar = StatusBar {
StatusBarItem(shortcut: "x", label: "test")
StatusBarItem(shortcut: "y", label: "test2")
}
#expect(statusBar.items.count == 2)
@@ -796,7 +796,7 @@ struct StatusBarItemsModifierTests {
func modifierCanBeApplied() {
let view = Text("Content")
.statusBarItems([
TStatusBarItem(shortcut: "q", label: "quit")
StatusBarItem(shortcut: "q", label: "quit")
])
// View should be wrapped in modifier
@@ -807,8 +807,8 @@ struct StatusBarItemsModifierTests {
func modifierWithBuilder() {
let view = Text("Content")
.statusBarItems {
TStatusBarItem(shortcut: "a", label: "alpha")
TStatusBarItem(shortcut: "b", label: "beta")
StatusBarItem(shortcut: "a", label: "alpha")
StatusBarItem(shortcut: "b", label: "beta")
}
#expect(view is StatusBarItemsModifier<Text>)
@@ -818,7 +818,7 @@ struct StatusBarItemsModifierTests {
func modifierWithContext() {
let view = Text("Dialog")
.statusBarItems(context: "dialog") {
TStatusBarItem(shortcut: Shortcut.escape, label: "close")
StatusBarItem(shortcut: Shortcut.escape, label: "close")
}
#expect(view is StatusBarItemsModifier<Text>)
@@ -834,7 +834,7 @@ struct StatusBarItemsModifierTests {
// Create view with modifier
let view = Text("Test")
.statusBarItems {
TStatusBarItem(shortcut: "t", label: "test")
StatusBarItem(shortcut: "t", label: "test")
}
// Render with environment
@@ -861,13 +861,13 @@ struct StatusBarItemsModifierTests {
// Set global items first
state.setItems([
TStatusBarItem(shortcut: "g", label: "global")
StatusBarItem(shortcut: "g", label: "global")
])
// Create view with context modifier
let view = Text("Dialog")
.statusBarItems(context: "dialog") {
TStatusBarItem(shortcut: "d", label: "dialog-item")
StatusBarItem(shortcut: "d", label: "dialog-item")
}
// Render
@@ -900,7 +900,7 @@ struct StatusBarItemsModifierTests {
let view = Text("Hello World")
.statusBarItems {
TStatusBarItem(shortcut: "x", label: "test")
StatusBarItem(shortcut: "x", label: "test")
}
let context = RenderContext(
@@ -920,8 +920,8 @@ struct StatusBarItemsModifierTests {
@Test("statusBarItems with array and context")
func modifierWithArrayAndContext() {
let items = [
TStatusBarItem(shortcut: "y", label: "yes"),
TStatusBarItem(shortcut: "n", label: "no")
StatusBarItem(shortcut: "y", label: "yes"),
StatusBarItem(shortcut: "n", label: "no")
]
let view = Text("Confirm?")
@@ -939,14 +939,14 @@ struct StatusBarItemsModifierTests {
// Outer sets global, inner pushes context
let innerView = Text("Inner")
.statusBarItems(context: "inner") {
TStatusBarItem(shortcut: "i", label: "inner-item")
StatusBarItem(shortcut: "i", label: "inner-item")
}
let outerView = VStack {
innerView
}
.statusBarItems {
TStatusBarItem(shortcut: "o", label: "outer-item")
StatusBarItem(shortcut: "o", label: "outer-item")
}
let context = RenderContext(
+10 -10
View File
@@ -1,15 +1,15 @@
//
// TViewTests.swift
// ViewTests.swift
// SwiftTUI
//
// Tests for the TView protocol, ViewBuilder, and basic views.
// Tests for the View protocol, ViewBuilder, and basic views.
//
import Testing
@testable import SwiftTUI
@Suite("TView Protocol Tests")
struct TViewTests {
@Suite("View Protocol Tests")
struct ViewTests {
@Test("Text view can be created")
func textViewCreation() {
@@ -57,8 +57,8 @@ struct ViewBuilderTests {
@Test("ViewBuilder with single view")
func singleView() {
@TViewBuilder
func buildView() -> some TView {
@ViewBuilder
func buildView() -> some View {
Text("Single")
}
@@ -68,8 +68,8 @@ struct ViewBuilderTests {
@Test("ViewBuilder with two views")
func twoViews() {
@TViewBuilder
func buildViews() -> some TView {
@ViewBuilder
func buildViews() -> some View {
Text("First")
Text("Second")
}
@@ -80,8 +80,8 @@ struct ViewBuilderTests {
@Test("ViewBuilder with three views")
func threeViews() {
@TViewBuilder
func buildViews() -> some TView {
@ViewBuilder
func buildViews() -> some View {
Text("One")
Text("Two")
Text("Three")