mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
/// // ...
|
||||
/// }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
+22
-22
@@ -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,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user