mirror of
https://github.com/phranck/TUIkit.git
synced 2026-05-21 09:50:35 +00:00
b3d563040a
- Add Image view rendering local files and URLs as colored ASCII art - Add CSTBImage C target wrapping stb_image for cross-platform image decoding - Add ASCIIConverter with block, ASCII, and braille character sets - Support trueColor, ANSI-256, grayscale, and mono color modes - Add Floyd-Steinberg dithering for improved visual quality - Add async image loading with URLImageCache for URL sources - Add bracketed paste mode for bulk text insertion in text fields - Add TextContentType modifier for input character filtering - Add ContentMode enum and aspectRatio(_:contentMode:) View modifier - Add text-input priority in key dispatch to prevent shortcut conflicts - Add Image (File) and Image (URL) demo pages to example app - Update DocC documentation with new symbols and layout table
165 lines
5.7 KiB
Swift
165 lines
5.7 KiB
Swift
// 🖥️ TUIKit — Terminal UI Kit for Swift
|
||
// ContentView.swift
|
||
//
|
||
// Created by LAYERED.work
|
||
// License: MIT
|
||
|
||
import TUIkit
|
||
|
||
// MARK: - Demo Page Enum
|
||
|
||
/// The available demo pages in the example app.
|
||
enum DemoPage: Int, CaseIterable {
|
||
case menu
|
||
case textStyles
|
||
case colors
|
||
case containers
|
||
case overlays
|
||
case layout
|
||
case buttons
|
||
case toggles
|
||
case textFields
|
||
case secureFields
|
||
case radioButtons
|
||
case spinners
|
||
case lists
|
||
case tables
|
||
case sliders
|
||
case steppers
|
||
case splitView
|
||
case imageFile
|
||
case imageURL
|
||
}
|
||
|
||
// MARK: - Content View (Page Router)
|
||
|
||
/// The main content view that switches between pages.
|
||
///
|
||
/// This view acts as a router, displaying the appropriate demo page
|
||
/// based on the current state. It uses `@State` for all reactive
|
||
/// properties — exactly like SwiftUI.
|
||
struct ContentView: View {
|
||
@State var currentPage: DemoPage = .menu
|
||
@State var menuSelection: Int = 0
|
||
|
||
var body: some View {
|
||
// Capture bindings for use in closures
|
||
let pageSetter = $currentPage
|
||
|
||
// Show current page based on state
|
||
// Note: Background color is set by AppRunner using theme.background
|
||
pageContent(for: currentPage, pageSetter: pageSetter)
|
||
.onKeyPress { event in
|
||
switch event.key {
|
||
case .escape:
|
||
// ESC goes back to menu (or exits if already on menu)
|
||
if currentPage != .menu {
|
||
pageSetter.wrappedValue = .menu
|
||
return true // Consumed
|
||
}
|
||
return false // Let default handler exit the app
|
||
default:
|
||
// Quick-jump shortcuts only work from the menu page.
|
||
// On sub-pages they would conflict with text input
|
||
// (e.g. TextField, SecureField).
|
||
guard currentPage == .menu else { return false }
|
||
return handleMenuShortcut(event.key)
|
||
}
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private func pageContent(for page: DemoPage, pageSetter: Binding<DemoPage>) -> some View {
|
||
switch page {
|
||
case .menu:
|
||
MainMenuPage(currentPage: $currentPage, menuSelection: $menuSelection)
|
||
.statusBarItems {
|
||
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "nav")
|
||
StatusBarItem(shortcut: Shortcut.enter, label: "select", key: .enter)
|
||
StatusBarItem(shortcut: Shortcut.range("1", "9") + ", 0", label: "jump")
|
||
}
|
||
case .textStyles:
|
||
TextStylesPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .colors:
|
||
ColorsPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .containers:
|
||
ContainersPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .overlays:
|
||
OverlaysPage(onBack: { pageSetter.wrappedValue = .menu })
|
||
case .layout:
|
||
LayoutPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .buttons:
|
||
ButtonsPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .toggles:
|
||
TogglePage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .textFields:
|
||
TextFieldPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .secureFields:
|
||
SecureFieldPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .radioButtons:
|
||
RadioButtonPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .spinners:
|
||
SpinnersPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .lists:
|
||
ListPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .tables:
|
||
TablePage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .sliders:
|
||
SliderPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .steppers:
|
||
StepperPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .splitView:
|
||
SplitViewPage()
|
||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||
case .imageFile:
|
||
ImageFilePage()
|
||
case .imageURL:
|
||
ImageURLPage()
|
||
}
|
||
}
|
||
|
||
/// Common status bar items for sub-pages.
|
||
private func subPageItems(pageSetter: Binding<DemoPage>) -> [any StatusBarItemProtocol] {
|
||
[
|
||
StatusBarItem(shortcut: Shortcut.escape, label: "back") { [pageSetter] in
|
||
pageSetter.wrappedValue = .menu
|
||
},
|
||
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"),
|
||
]
|
||
}
|
||
|
||
/// Handles quick-jump shortcuts from the menu page.
|
||
///
|
||
/// - Returns: `true` if the key was consumed, `false` otherwise.
|
||
private func handleMenuShortcut(_ key: Key) -> Bool {
|
||
let mapping: [Character: DemoPage] = [
|
||
"1": .textStyles, "2": .colors, "3": .containers,
|
||
"4": .overlays, "5": .layout, "6": .buttons,
|
||
"7": .toggles, "8": .textFields, "\\": .secureFields,
|
||
"9": .radioButtons, "0": .spinners, "-": .lists,
|
||
"=": .tables, "[": .sliders, "]": .steppers,
|
||
";": .splitView, "'": .imageFile, ",": .imageURL,
|
||
]
|
||
|
||
if case .character(let ch) = key, let page = mapping[ch] {
|
||
currentPage = page
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
}
|