mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Implement SwiftUI-style structural identity so @State values survive full view-tree reconstruction on every render pass. State is keyed by ViewIdentity (path-based) and property index in a central StateStorage. - Add ViewIdentity (path-based structural identity for views) - Add StateStorage with GC and branch invalidation - Refactor @State to self-hydrate from StateStorage during init - Set root hydration context in RenderLoop before app.body evaluation - Extend RenderContext with identity propagation helpers - Add ConditionalView branch invalidation on switch - Remove scene cache from RenderLoop (app.body evaluated fresh per frame) - Enhance Example App pages with @State (ButtonsPage, OverlaysPage, ContainersPage) - Update DocC articles (StateManagement, RenderCycle) - Add 12 tests for state storage identity
94 lines
2.9 KiB
Swift
94 lines
2.9 KiB
Swift
//
|
|
// ButtonsPage.swift
|
|
// TUIkitExample
|
|
//
|
|
// Demonstrates button and focus system capabilities.
|
|
//
|
|
|
|
import TUIkit
|
|
|
|
/// Buttons and focus demo page.
|
|
///
|
|
/// Shows interactive button features including:
|
|
/// - Different button styles (default, primary, success, destructive)
|
|
/// - Disabled buttons
|
|
/// - Plain style (no border)
|
|
/// - ButtonRow for horizontal groups
|
|
/// - Focus navigation with Tab
|
|
/// - Live click counter demonstrating `@State` persistence across re-renders
|
|
struct ButtonsPage: View {
|
|
@State var clickCount: Int = 0
|
|
|
|
var body: some View {
|
|
VStack(spacing: 1) {
|
|
HeaderView(title: "Buttons & Focus Demo")
|
|
|
|
DemoSection("Interactive Counter (@State)") {
|
|
HStack(spacing: 2) {
|
|
Button("+1", style: .primary) {
|
|
clickCount += 1
|
|
}
|
|
Button("+10", style: .success) {
|
|
clickCount += 10
|
|
}
|
|
Button("Reset", style: .destructive) {
|
|
clickCount = 0
|
|
}
|
|
Text("Clicks: \(clickCount)")
|
|
.bold()
|
|
.foregroundColor(.palette.accent)
|
|
}
|
|
}
|
|
|
|
DemoSection("Button Styles") {
|
|
HStack(spacing: 2) {
|
|
Button("Default") {
|
|
clickCount += 1
|
|
}
|
|
Button("Primary", style: .primary) {
|
|
clickCount += 1
|
|
}
|
|
Button("Success", style: .success) {
|
|
clickCount += 1
|
|
}
|
|
Button("Destructive", style: .destructive) {
|
|
clickCount += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
DemoSection("Disabled Button") {
|
|
HStack(spacing: 2) {
|
|
Button("Enabled") { clickCount += 1 }
|
|
Button("Disabled") {}.disabled()
|
|
}
|
|
}
|
|
|
|
DemoSection("Plain Style (No Border)") {
|
|
HStack(spacing: 2) {
|
|
Button("Link 1", style: .plain) { clickCount += 1 }
|
|
Button("Link 2", style: .plain) { clickCount += 1 }
|
|
}
|
|
}
|
|
|
|
DemoSection("ButtonRow (Horizontal Group)") {
|
|
ButtonRow(spacing: 3) {
|
|
Button("Cancel") { clickCount += 1 }
|
|
Button("Save", style: .primary) { clickCount += 1 }
|
|
}
|
|
}
|
|
|
|
DemoSection("Focus Navigation") {
|
|
VStack {
|
|
Text("Use [Tab] to move focus between buttons")
|
|
.dim()
|
|
Text("Use [Enter] or [Space] to press the focused button")
|
|
.dim()
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|