├── .gitignore ├── .vscode └── launch.json ├── Package.swift ├── README.md └── Sources ├── Common ├── Alignment.swift ├── CommonUtility.swift ├── Edge.swift └── IsDebug.swift ├── Components ├── AnyView.swift ├── Button.swift ├── ClientDefinedViewNodeBuilder.swift ├── ConditionalView.swift ├── EmptyView.swift ├── HStack.swift ├── Screen.swift ├── Text.swift ├── TupleView.swift ├── VStack.swift └── ZStack.swift ├── Core ├── App.swift ├── AppEngine.swift ├── Environment.swift ├── FocusEngine.swift ├── Identifier.swift ├── Node.swift ├── RenderContext.swift ├── RenderEngine.swift ├── State.swift ├── Terminal.swift ├── TreeEngine.swift ├── View.swift └── ViewBuilder.swift ├── ViewModifierComponents ├── Border.swift ├── Frame.swift ├── Padding.swift └── SetEnvironment.swift └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "args": ["debug"], 7 | "cwd": "${workspaceFolder:BlinkUI}", 8 | "name": "Debug BlinkUI", 9 | "program": "${workspaceFolder:BlinkUI}/.build/debug/BlinkUI", 10 | "preLaunchTask": "swift: Build Debug BlinkUI" 11 | }, 12 | { 13 | "type": "swift", 14 | "request": "launch", 15 | "args": [], 16 | "cwd": "${workspaceFolder:BlinkUI}", 17 | "name": "Release BlinkUI", 18 | "program": "${workspaceFolder:BlinkUI}/.build/release/BlinkUI", 19 | "preLaunchTask": "swift: Build Release BlinkUI" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "BlinkUI", 8 | platforms: [.macOS(.v14)], 9 | targets: [ 10 | // Targets are the basic building blocks of a package, defining a module or a test suite. 11 | // Targets can depend on other targets in this package and products from dependencies. 12 | .executableTarget( 13 | name: "BlinkUI") 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlinkUI: A SwiftUI-Inspired Terminal UI Framework 2 | 3 | BlinkUI is an experimental terminal UI framework that brings SwiftUI's declarative syntax and component-based architecture to terminal applications. Build beautiful, interactive terminal UIs using familiar SwiftUI-like patterns. 4 | 5 | ![BlinkUI_SimpleDemo](https://github.com/user-attachments/assets/5567be9d-2510-4e39-b430-ba380cc36a6f) 6 | 7 | > [!WARNING] 8 | > This is an experimental project, currently in active development. While functional, it's not yet recommended for production use. Feedback and contributions are welcome! 9 | 10 | ## Features 11 | 12 | - **SwiftUI-like Syntax**: Write terminal UIs using familiar declarative syntax 13 | - **Rich Component Library**: Built-in components like Text, Button, HStack, VStack, and ZStack 14 | - **State Management**: Support for @State, @Binding, and @Environment property wrappers 15 | - **Focus Engine**: Keyboard navigation between interactive elements (Tab/Shift+Tab) 16 | - **View Modifiers**: Padding, borders, and frame customization 17 | - **Layout System**: Flexible layout engine for component positioning 18 | 19 | ## Quick Example 20 | 21 | ```swift 22 | import BlinkUI 23 | 24 | struct Example: App { 25 | @State var isSelected: Bool = false 26 | 27 | var body: some View { 28 | Text("This is your last chance. After this, there is no turning back.") 29 | if !isSelected { 30 | HStack { 31 | Button(action: { isSelected = true }) { Text("Blue pill") } 32 | Button(action: { isSelected = true }) { Text("Red pill") } 33 | } 34 | } else { 35 | Text("The Matrix is everywhere. It is all around us.") 36 | } 37 | } 38 | } 39 | 40 | // Run the app 41 | let engine = AppEngine(app: Example()) 42 | engine.run() 43 | ``` 44 | 45 | ## Components and Features 46 | 47 | ### Base Views 48 | 49 | #### Text 50 | Text with simple word wrapping logic 51 |
52 | Text Component Example 53 |
54 | 55 | #### Button 56 | Interactive buttons with customizable labels and actions. 57 |
58 | Button Example 1 59 | Button Example 2 60 |
61 | 62 | #### HStack 63 |
64 | image 65 | image 66 | image 67 |
68 | 69 | #### VStack 70 |
71 | image 72 | image 73 | image 74 |
75 | 76 | #### ZStack 77 |
78 | ZStack Example 79 |
80 | 81 | ### View Modifiers 82 | 83 | Powerful modifiers to customize your components: 84 | 85 | #### Frame 86 | Control the dimensions and alignment of your components. 87 |
88 | Frame Modifier Example 89 |
90 | 91 | #### Padding 92 | Add space around your components for better layout control. 93 |
94 | Padding Modifier Example 95 |
96 | 97 | #### Border 98 | Add beautiful borders with different styles. 99 |
100 | Border Modifier Example 101 |
102 | 103 | ### Focus Engine 104 | Navigate through interactive elements using keyboard (Tab/Shift+Tab). 105 | ![Focus Navigation Demo](https://github.com/user-attachments/assets/5e04c90a-074a-4066-b3e2-660520c8d385) 106 | 107 | ### State Management 108 | Reactive state management with property wrappers (@State, @Binding, @Environment). 109 |
110 | State Management Example 111 |
112 | 113 | ## Under Development 114 | - 🚧 Word wrapping 115 | - 🚧 Advanced layouts 116 | - 🚧 Performance optimization 117 | - 🚧 Testing framework 118 | - 🚧 Buffer management 119 | - 🚧 Cross-platform support (Windows/Linux) 120 | - 🚧 Hyperlink support 121 | - 🚧 Documentation and examples 122 | 123 | ## Getting Started 124 | 125 | > Coming Soon: Installation and usage instructions will be added as the project stabilizes. 126 | 127 | ## Contributing 128 | 129 | This project is open for experimentation and learning. If you're interested in terminal UI frameworks or SwiftUI internals, feel free to take a look at the code and provide feedback. 130 | 131 | ## Technical Details 132 | 133 | The framework consists of over 2,000 lines of code implementing: 134 | - Custom ViewBuilder for declarative syntax 135 | - Node-based layout system 136 | - State management system 137 | - Focus engine for accessibility 138 | - Terminal rendering engine 139 | 140 | > TODO: A detailed technical blog post about the implementation and learnings is planned. 141 | 142 | ## Why This Project? 143 | 144 | BlinkUI started as a deep dive into understanding SwiftUI's architecture. Instead of just reading about SwiftUI or building another todo app, I decided to challenge myself by recreating its core concepts for terminal applications. This hands-on approach provided invaluable insights into: 145 | 146 | - How declarative UI frameworks actually work under the hood 147 | - The complexities of state management and data flow 148 | - The challenges of building a layout engine from scratch 149 | - Real-world application of property wrappers and function builders 150 | 151 | -------------------------------------------------------------------------------- /Sources/Common/Alignment.swift: -------------------------------------------------------------------------------- 1 | public enum Alignment: Sendable { 2 | case topLeading, top, topTrailing 3 | case leading, center, trailing 4 | case bottomLeading, bottom, bottomTrailing 5 | } 6 | public struct HorizontalAlignment: Sendable, Equatable { 7 | let alignment: Alignment 8 | public static let leading: HorizontalAlignment = .init(alignment: .leading) 9 | public static let center: HorizontalAlignment = .init(alignment: .center) 10 | public static let trailing: HorizontalAlignment = .init(alignment: .trailing) 11 | } 12 | public struct VerticalAlignment: Sendable, Equatable { 13 | let alignment: Alignment 14 | public static let top: VerticalAlignment = .init(alignment: .top) 15 | public static let center: VerticalAlignment = .init(alignment: .center) 16 | public static let bottom: VerticalAlignment = .init(alignment: .bottom) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Common/CommonUtility.swift: -------------------------------------------------------------------------------- 1 | public typealias Size = (width: Int, height: Int) 2 | public typealias Point = (x: Int, y: Int) 3 | 4 | extension Int { 5 | public static var infinity: Int { 6 | return Int.max 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Common/Edge.swift: -------------------------------------------------------------------------------- 1 | public enum Edge { 2 | case top 3 | case bottom 4 | case leading 5 | case trailing 6 | } 7 | public enum EdgeSet { 8 | case all 9 | case top 10 | case bottom 11 | case leading 12 | case trailing 13 | case horizontal 14 | case vertical 15 | 16 | var set: Set { 17 | switch self { 18 | case .all: 19 | return [.top, .bottom, .leading, .trailing] 20 | case .top: 21 | return [.top] 22 | case .bottom: 23 | return [.bottom] 24 | case .leading: 25 | return [.leading] 26 | case .trailing: 27 | return [.trailing] 28 | case .horizontal: 29 | return [.leading, .trailing] 30 | case .vertical: 31 | return [.top, .bottom] 32 | } 33 | } 34 | 35 | func contains(_ edge: Edge) -> Bool { 36 | return set.contains(edge) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Common/IsDebug.swift: -------------------------------------------------------------------------------- 1 | // Is debug i.e. launched via VS Code 2 | func IsDebug() -> Bool { 3 | return CommandLine.arguments.contains("debug") 4 | } 5 | -------------------------------------------------------------------------------- /Sources/Components/AnyView.swift: -------------------------------------------------------------------------------- 1 | public struct AnyView: View { 2 | let content: any View 3 | 4 | init(@ViewBuilder content: () -> any View) { 5 | self.content = content() 6 | } 7 | } 8 | extension AnyView: NodeBuilder { 9 | func childViews() -> [any View] { 10 | return [content] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Components/Button.swift: -------------------------------------------------------------------------------- 1 | public struct Button