mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
feat: Add comprehensive DocC documentation with tutorials
Add complete Apple-style DocC documentation for TUIKit framework: ## Documentation Structure - Main landing page (TUIKit.md) with quick-start and topic navigation - 8 guide articles: GettingStarted, ViewHierarchy, StateManagement, Theming, Appearance, Focus, Modifiers, Architecture - 3 step-by-step tutorials: BuildYourFirstApp, BuildInteractiveMenu, BuildThemableUI - Documentation catalog with proper Info.plist configuration ## Code Improvements - Enhanced Box.swift documentation with clear container comparison - Added comprehensive examples and usage patterns - Explained appearance integration and sizing behavior ## Infrastructure - GitHub Actions workflow (.github/workflows/docc.yml) for automatic DocC building - Configured for deployment to GitHub Pages on push to main - Uses xcrun docc build with proper artifact handling ## Documentation Includes - API reference stubs for all major components - Comprehensive guide articles with code examples - Interactive tutorials following Apple's documentation style - Cross-references using DocC syntax (``Type``) - Best practices and design patterns explained The documentation is ready for automated building and deployment to GitHub Pages.
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
# Building an Interactive Menu
|
||||
|
||||
Create a navigation menu with keyboard shortcuts and status bar hints.
|
||||
|
||||
@Intro(title: "Build an Interactive Menu App") {
|
||||
Learn how to create a functional menu system with keyboard navigation,
|
||||
selection handling, and status bar integration.
|
||||
|
||||
You'll build a simple settings menu that demonstrates focus management,
|
||||
keyboard events, and status bar items.
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
In this tutorial, you'll create a menu-driven application featuring:
|
||||
|
||||
- `Menu` component for selection
|
||||
- Keyboard shortcut handling
|
||||
- Status bar with hints
|
||||
- Page navigation patterns
|
||||
|
||||
@Section(title: "Create the Menu Component") {
|
||||
@ContentAndMedia {
|
||||
Start with a basic menu that displays options and tracks selection.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Create your app with a `Menu`:
|
||||
|
||||
```swift
|
||||
import TUIKit
|
||||
|
||||
@main
|
||||
struct MenuApp: App {
|
||||
@State private var selectedOption: String = "home"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 2) {
|
||||
Text("Main Menu")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Menu(
|
||||
items: [
|
||||
MenuItem("📄 View Profile", id: "profile"),
|
||||
MenuItem("⚙️ Settings", id: "settings"),
|
||||
MenuItem("💾 Save Data", id: "save"),
|
||||
MenuItem("❌ Exit", id: "exit")
|
||||
],
|
||||
selection: $selectedOption
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Selected: \(selectedOption)")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `Menu` creates an interactive selectable list
|
||||
- `$selectedOption` creates a two-way binding
|
||||
- Use Tab/Arrow keys to navigate
|
||||
- Press Enter to confirm selection
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Test navigating with Tab, arrow keys, and Enter.
|
||||
The "Selected" text updates as you navigate.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Page Navigation") {
|
||||
@ContentAndMedia {
|
||||
Create different pages that show based on menu selection.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add conditional pages:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct MenuApp: App {
|
||||
@State private var selectedOption: String = "home"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if selectedOption == "exit" {
|
||||
VStack {
|
||||
Text("Exiting...")
|
||||
}
|
||||
} else {
|
||||
VStack(spacing: 2) {
|
||||
Text("Main Menu")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Menu(
|
||||
items: [
|
||||
MenuItem("📄 View Profile", id: "profile"),
|
||||
MenuItem("⚙️ Settings", id: "settings"),
|
||||
MenuItem("💾 Save Data", id: "save"),
|
||||
MenuItem("❌ Exit", id: "exit")
|
||||
],
|
||||
selection: $selectedOption
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
currentPageContent
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var currentPageContent: some View {
|
||||
switch selectedOption {
|
||||
case "profile":
|
||||
VStack(spacing: 1) {
|
||||
Text("📄 Profile Information").bold()
|
||||
Text("Name: John Doe")
|
||||
Text("Role: Developer")
|
||||
}
|
||||
case "settings":
|
||||
VStack(spacing: 1) {
|
||||
Text("⚙️ Application Settings").bold()
|
||||
Text("Theme: Green")
|
||||
Text("Language: English")
|
||||
}
|
||||
case "save":
|
||||
Text("✓ Data saved successfully!")
|
||||
.foregroundColor(.theme.success)
|
||||
default:
|
||||
Text("Select an option above")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `currentPageContent` computed property shows different content
|
||||
based on the selected menu item.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run and test navigation:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Navigate to each menu item and see the page content change.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Status Bar Hints") {
|
||||
@ContentAndMedia {
|
||||
Add a status bar at the bottom with helpful keyboard hints.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add status bar items:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct MenuApp: App {
|
||||
@State private var selectedOption: String = "home"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if selectedOption == "exit" {
|
||||
VStack {
|
||||
Text("Exiting...")
|
||||
}
|
||||
} else {
|
||||
VStack(spacing: 2) {
|
||||
Text("Main Menu")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Menu(
|
||||
items: [
|
||||
MenuItem("📄 View Profile", id: "profile"),
|
||||
MenuItem("⚙️ Settings", id: "settings"),
|
||||
MenuItem("💾 Save Data", id: "save"),
|
||||
MenuItem("❌ Exit", id: "exit")
|
||||
],
|
||||
selection: $selectedOption
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
currentPageContent
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.statusBarItems {
|
||||
StatusBarItem(
|
||||
label: "Select",
|
||||
shortcut: Shortcut.enter,
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Theme",
|
||||
shortcut: Shortcut.letter("t"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Help",
|
||||
shortcut: Shortcut.letter("?"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Quit",
|
||||
shortcut: Shortcut.letter("q"),
|
||||
action: { }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var currentPageContent: some View {
|
||||
switch selectedOption {
|
||||
case "profile":
|
||||
VStack(spacing: 1) {
|
||||
Text("📄 Profile Information").bold()
|
||||
Text("Name: John Doe")
|
||||
Text("Role: Developer")
|
||||
}
|
||||
case "settings":
|
||||
VStack(spacing: 1) {
|
||||
Text("⚙️ Application Settings").bold()
|
||||
Text("Theme: Green")
|
||||
Text("Language: English")
|
||||
}
|
||||
case "save":
|
||||
Text("✓ Data saved successfully!")
|
||||
.foregroundColor(.theme.success)
|
||||
default:
|
||||
Text("Select an option above")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`statusBarItems` adds a bottom status bar with keyboard shortcuts.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
The status bar appears at the bottom showing available shortcuts.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Keyboard Shortcuts") {
|
||||
@ContentAndMedia {
|
||||
Handle custom keyboard shortcuts to navigate directly to menu items.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add keyboard event handling:
|
||||
|
||||
```swift
|
||||
.onKeyPress { event in
|
||||
switch event.key {
|
||||
case .character("1"):
|
||||
selectedOption = "profile"
|
||||
return true
|
||||
case .character("2"):
|
||||
selectedOption = "settings"
|
||||
return true
|
||||
case .character("3"):
|
||||
selectedOption = "save"
|
||||
return true
|
||||
case .character("e"):
|
||||
selectedOption = "exit"
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add this modifier to your main `VStack`. Now users can:
|
||||
- Press `1` to go to Profile
|
||||
- Press `2` to go to Settings
|
||||
- Press `3` to Save
|
||||
- Press `e` to Exit
|
||||
}
|
||||
|
||||
@Step {
|
||||
Update the status bar to show these shortcuts:
|
||||
|
||||
```swift
|
||||
.statusBarItems {
|
||||
StatusBarItem(
|
||||
label: "Profile",
|
||||
shortcut: Shortcut.digit("1"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Settings",
|
||||
shortcut: Shortcut.digit("2"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Save",
|
||||
shortcut: Shortcut.digit("3"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Exit",
|
||||
shortcut: Shortcut.letter("e"),
|
||||
action: { }
|
||||
)
|
||||
}
|
||||
```
|
||||
}
|
||||
|
||||
@Step {
|
||||
Test your shortcuts:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Press number keys to jump directly to menu items.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Next Steps
|
||||
|
||||
You've created an interactive menu-driven application!
|
||||
|
||||
- Learn about <doc:Focus> for advanced focus management
|
||||
- Explore <doc:StateManagement> for more complex state patterns
|
||||
- Try building a <doc:BuildThemableUI> with theme switching
|
||||
- Check out <doc:Appearance> for custom styling
|
||||
@@ -0,0 +1,421 @@
|
||||
# Building a Themable UI
|
||||
|
||||
Create an application with dynamic theme switching and persistence.
|
||||
|
||||
@Intro(title: "Build a Themable App") {
|
||||
Learn how to integrate TUIKit's theming system into your application,
|
||||
allowing users to switch between themes and persist their preference.
|
||||
|
||||
You'll work with the theme environment, theme manager, and storage to
|
||||
create a professional themable application.
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
In this tutorial, you'll create an app that features:
|
||||
|
||||
- Theme environment integration
|
||||
- Dynamic theme switching
|
||||
- Persisted theme preference
|
||||
- Visual theme selection UI
|
||||
|
||||
@Section(title: "Set Up Theme Storage") {
|
||||
@ContentAndMedia {
|
||||
Use `@AppStorage` to remember the user's theme choice across sessions.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Create an app that stores the selected theme:
|
||||
|
||||
```swift
|
||||
import TUIKit
|
||||
|
||||
@main
|
||||
struct ThemableApp: App {
|
||||
@AppStorage("selectedTheme") var selectedThemeName: String = "green"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(\.theme, themeForName(selectedThemeName))
|
||||
}
|
||||
}
|
||||
|
||||
func themeForName(_ name: String) -> Theme {
|
||||
switch name {
|
||||
case "amber":
|
||||
return AmberPhosphorTheme()
|
||||
case "white":
|
||||
return WhitePhosphorTheme()
|
||||
case "red":
|
||||
return RedPhosphorTheme()
|
||||
default:
|
||||
return GreenPhosphorTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
Text("Themable Application")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Select a theme to customize the appearance")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@AppStorage("selectedTheme")` automatically persists the theme choice.
|
||||
When the app restarts, it loads the saved theme.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run your app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
The app launches with the default Green theme.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Create Theme Selection UI") {
|
||||
@ContentAndMedia {
|
||||
Display the 4 available themes and let users select one.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add a theme menu:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct ThemableApp: App {
|
||||
@AppStorage("selectedTheme") var selectedThemeName: String = "green"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(selectedThemeName: $selectedThemeName)
|
||||
.environment(\.theme, themeForName(selectedThemeName))
|
||||
}
|
||||
}
|
||||
|
||||
func themeForName(_ name: String) -> Theme {
|
||||
// ... same as before
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@Binding var selectedThemeName: String
|
||||
|
||||
let themes = [
|
||||
("green", "🟢 Green (Default)"),
|
||||
("amber", "🟡 Amber"),
|
||||
("white", "⚪ White"),
|
||||
("red", "🔴 Red")
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
Text("Themable Application")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Select a theme:")
|
||||
.bold()
|
||||
|
||||
Menu(
|
||||
items: themes.map { id, label in
|
||||
MenuItem(label, id: id)
|
||||
},
|
||||
selection: $selectedThemeName
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Current: \(themeName(selectedThemeName))")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
func themeName(_ id: String) -> String {
|
||||
themes.first(where: { $0.0 == id })?.1 ?? id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pass `$selectedThemeName` as a binding to allow the menu to change it.
|
||||
The environment automatically updates because it depends on this value.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Test theme switching:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Use Tab/Arrow keys to select different themes.
|
||||
Notice the colors change immediately!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Display Theme Colors") {
|
||||
@ContentAndMedia {
|
||||
Show a visual preview of the current theme's colors.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add a color preview:
|
||||
|
||||
```swift
|
||||
struct ContentView: View {
|
||||
@Binding var selectedThemeName: String
|
||||
@Environment(\.theme) var theme
|
||||
|
||||
let themes = [
|
||||
("green", "🟢 Green (Default)"),
|
||||
("amber", "🟡 Amber"),
|
||||
("white", "⚪ White"),
|
||||
("red", "🔴 Red")
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
Text("Themable Application")
|
||||
.bold()
|
||||
.foregroundColor(.theme.accent)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Select a theme:").bold()
|
||||
|
||||
Menu(
|
||||
items: themes.map { id, label in
|
||||
MenuItem(label, id: id)
|
||||
},
|
||||
selection: $selectedThemeName
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Color preview box
|
||||
VStack(spacing: 1) {
|
||||
Text("Theme Colors")
|
||||
.bold()
|
||||
|
||||
HStack(spacing: 1) {
|
||||
Text(" ").background(theme.foreground)
|
||||
Text(" ").background(theme.accent)
|
||||
Text(" ").background(theme.border)
|
||||
Text(" ").background(theme.warning)
|
||||
}
|
||||
|
||||
Text("Foreground | Accent | Border | Warning")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
}
|
||||
.padding(1)
|
||||
.border(.rounded)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
func themeName(_ id: String) -> String {
|
||||
themes.first(where: { $0.0 == id })?.1 ?? id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@Environment(\.theme)` gives access to the current theme.
|
||||
Use `theme.foreground`, `theme.accent`, etc. to access colors.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Switch themes and see the color preview update.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Theme Shortcuts and Status Bar") {
|
||||
@ContentAndMedia {
|
||||
Let users cycle themes quickly with keyboard shortcuts.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add quick theme cycling:
|
||||
|
||||
```swift
|
||||
struct ContentView: View {
|
||||
@Binding var selectedThemeName: String
|
||||
@Environment(\.theme) var theme
|
||||
|
||||
let themes = [
|
||||
("green", "🟢 Green (Default)"),
|
||||
("amber", "🟡 Amber"),
|
||||
("white", "⚪ White"),
|
||||
("red", "🔴 Red")
|
||||
]
|
||||
|
||||
let themeIds = ["green", "amber", "white", "red"]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
// ... menu UI as before ...
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.onKeyPress { event in
|
||||
if event.key == .character("t") {
|
||||
// Cycle to next theme
|
||||
if let currentIndex = themeIds.firstIndex(of: selectedThemeName) {
|
||||
let nextIndex = (currentIndex + 1) % themeIds.count
|
||||
selectedThemeName = themeIds[nextIndex]
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
.statusBarItems {
|
||||
StatusBarItem(
|
||||
label: "Cycle Theme",
|
||||
shortcut: Shortcut.letter("t"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Help",
|
||||
shortcut: Shortcut.letter("?"),
|
||||
action: { }
|
||||
)
|
||||
|
||||
StatusBarItem(
|
||||
label: "Quit",
|
||||
shortcut: Shortcut.letter("q"),
|
||||
action: { }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func themeName(_ id: String) -> String {
|
||||
themes.first(where: { $0.0 == id })?.1 ?? id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now users can press `t` to quickly cycle through themes.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Test theme cycling:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Press `t` repeatedly to cycle through all 4 themes.
|
||||
The selection updates and the preview colors change.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Enhance with Appearance Switching") {
|
||||
@ContentAndMedia {
|
||||
Add appearance style switching alongside theme switching.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add appearance cycling:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct ThemableApp: App {
|
||||
@AppStorage("selectedTheme") var selectedThemeName: String = "green"
|
||||
@AppStorage("selectedAppearance") var selectedAppearance: String = "rounded"
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(
|
||||
selectedThemeName: $selectedThemeName,
|
||||
selectedAppearance: $selectedAppearance
|
||||
)
|
||||
.environment(\.theme, themeForName(selectedThemeName))
|
||||
.environment(\.appearance, appearanceForName(selectedAppearance))
|
||||
}
|
||||
}
|
||||
|
||||
func themeForName(_ name: String) -> Theme {
|
||||
// ... existing code ...
|
||||
}
|
||||
|
||||
func appearanceForName(_ name: String) -> Appearance {
|
||||
switch name {
|
||||
case "line":
|
||||
return .line
|
||||
case "doubled":
|
||||
return .doubleLine
|
||||
case "heavy":
|
||||
return .heavy
|
||||
case "block":
|
||||
return .block
|
||||
default:
|
||||
return .rounded
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now users can customize both theme AND appearance style!
|
||||
}
|
||||
|
||||
@Step {
|
||||
The changes are automatically persisted with `@AppStorage`.
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Select a theme and appearance, exit with `q`, and run again.
|
||||
Your selection is remembered!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations! You've built a fully themed, customizable application.
|
||||
|
||||
- Learn more about <doc:Theming> for creating custom themes
|
||||
- Explore <doc:Appearance> for structural styles
|
||||
- See <doc:StateManagement> for advanced state patterns
|
||||
- Check the <doc:Architecture> overview for deeper understanding
|
||||
@@ -0,0 +1,270 @@
|
||||
# Building Your First App
|
||||
|
||||
Create a simple counter application to learn TUIKit basics.
|
||||
|
||||
@Intro(title: "Create Your First TUIKit App") {
|
||||
Learn the fundamentals of building a terminal user interface with TUIKit
|
||||
by creating a simple counter application.
|
||||
|
||||
You'll learn how to set up an `@main` app, use `@State` for local state,
|
||||
add interactive buttons, and use basic layout with `VStack` and `HStack`.
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
In this tutorial, you'll build a working counter app with increment/decrement buttons.
|
||||
This will teach you:
|
||||
|
||||
- Creating an app with the `@main` attribute
|
||||
- Using `VStack` and `HStack` for layout
|
||||
- Handling button taps with `@State`
|
||||
- Running and testing your app
|
||||
|
||||
@Section(title: "Create the App Entry Point") {
|
||||
@ContentAndMedia {
|
||||
Every TUIKit app needs an entry point decorated with `@main`.
|
||||
This tells Swift to use your app as the entry point for the program.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Create a new file called `main.swift` in your project:
|
||||
|
||||
```swift
|
||||
import TUIKit
|
||||
|
||||
@main
|
||||
struct CounterApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
Text("Hello, TUIKit!")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `@main` attribute marks this as the app entry point.
|
||||
`WindowGroup` represents the main window of your terminal app.
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run your app with:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
You should see "Hello, TUIKit!" displayed in the terminal.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Layout and Text") {
|
||||
@ContentAndMedia {
|
||||
Now let's organize content with `VStack` (vertical layout).
|
||||
We'll add a title and multiple text lines.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Modify the `body` to use `VStack`:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct CounterApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 1) {
|
||||
Text("Counter App")
|
||||
.bold()
|
||||
|
||||
Text("Simple counter to learn TUIKit")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Current count: 0")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `VStack(spacing: 1)` arranges content vertically with 1-unit spacing
|
||||
- `.bold()` makes the title bold
|
||||
- `.foregroundColor()` colors text
|
||||
- `Spacer()` creates flexible vertical space
|
||||
- `.padding()` adds space around all edges
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run the app again:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Now you should see a nicely formatted display with title,
|
||||
description, and spacing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Interactive State") {
|
||||
@ContentAndMedia {
|
||||
Use `@State` to track the counter value and re-render when it changes.
|
||||
This makes your app interactive!
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add a `@State` property for the counter:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct CounterApp: App {
|
||||
@State private var count: Int = 0
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 1) {
|
||||
Text("Counter App")
|
||||
.bold()
|
||||
|
||||
Text("Simple counter to learn TUIKit")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Current count: \(count)")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `@State` property wrapper stores local state. When `count` changes,
|
||||
the view automatically re-renders.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Add Buttons") {
|
||||
@ContentAndMedia {
|
||||
Now add buttons to increment and decrement the counter.
|
||||
Use `HStack` to arrange them horizontally.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Add buttons using `HStack`:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct CounterApp: App {
|
||||
@State private var count: Int = 0
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 1) {
|
||||
Text("Counter App")
|
||||
.bold()
|
||||
|
||||
Text("Simple counter to learn TUIKit")
|
||||
.foregroundColor(.theme.foregroundSecondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Current count: \(count)")
|
||||
|
||||
HStack(spacing: 2) {
|
||||
Button("Decrement") { count -= 1 }
|
||||
Button("Increment") { count += 1 }
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Press 'q' to quit")
|
||||
.foregroundColor(.theme.foregroundTertiary)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `HStack(spacing: 2)` arranges buttons horizontally
|
||||
- `Button(label) { action }` creates a clickable button
|
||||
- The closure after the label runs when the button is pressed
|
||||
}
|
||||
|
||||
@Step {
|
||||
Run your app:
|
||||
|
||||
```bash
|
||||
swift run
|
||||
```
|
||||
|
||||
Now use Tab to navigate to the buttons and press Enter to increment/decrement.
|
||||
The counter value updates in real-time!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Section(title: "Test and Iterate") {
|
||||
@ContentAndMedia {
|
||||
Test your counter app and try making improvements.
|
||||
}
|
||||
|
||||
@Steps {
|
||||
@Step {
|
||||
Test the following interactions:
|
||||
|
||||
- Press `Tab` to move focus to the next button
|
||||
- Press `Shift+Tab` to move focus backward
|
||||
- Press `Enter` when a button is focused to activate it
|
||||
- Watch the counter update
|
||||
- Press `q` to quit the app
|
||||
- Press `t` to cycle through themes
|
||||
- Press `a` to cycle through appearance styles
|
||||
}
|
||||
|
||||
@Step {
|
||||
Try these enhancements:
|
||||
|
||||
Add a reset button:
|
||||
|
||||
```swift
|
||||
HStack(spacing: 2) {
|
||||
Button("Decrement") { count = max(0, count - 1) }
|
||||
Button("Reset") { count = 0 }
|
||||
Button("Increment") { count += 1 }
|
||||
}
|
||||
```
|
||||
|
||||
Or add a border around the counter:
|
||||
|
||||
```swift
|
||||
Text("Current count: \(count)")
|
||||
.bold()
|
||||
.padding(1)
|
||||
.border(.rounded)
|
||||
```
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations! You've built your first TUIKit app.
|
||||
|
||||
- Learn more about <doc:StateManagement> for complex state scenarios
|
||||
- Explore <doc:Theming> to customize colors
|
||||
- Try building an interactive <doc:BuildInteractiveMenu>
|
||||
- Check out <doc:Modifiers> for more styling options
|
||||
Reference in New Issue
Block a user