mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Fix: Replace CFAbsoluteTimeGetCurrent with Date for Linux compatibility
This commit is contained in:
@@ -21,11 +21,11 @@ struct RenderBottleneckTests {
|
||||
}
|
||||
|
||||
private func measure(_ name: String, iterations: Int = 1000, block: () -> Void) -> TimeInterval {
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
let start = Date()
|
||||
for _ in 0..<iterations {
|
||||
block()
|
||||
}
|
||||
let time = CFAbsoluteTimeGetCurrent() - start
|
||||
let time = Date().timeIntervalSince(start)
|
||||
let perIteration = (time / Double(iterations)) * 1000
|
||||
print(" \(name): \(String(format: "%.3f", perIteration))ms per iteration")
|
||||
return time
|
||||
|
||||
@@ -33,12 +33,11 @@ struct RenderPerformanceTests {
|
||||
iterations: Int = 100,
|
||||
context: RenderContext
|
||||
) -> TimeInterval {
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
let start = Date()
|
||||
for _ in 0..<iterations {
|
||||
_ = renderToBuffer(view, context: context)
|
||||
}
|
||||
let end = CFAbsoluteTimeGetCurrent()
|
||||
return end - start
|
||||
return Date().timeIntervalSince(start)
|
||||
}
|
||||
|
||||
// MARK: - Stack Performance Tests
|
||||
@@ -290,30 +289,30 @@ struct RenderPerformanceStatistics {
|
||||
var results: [(String, TimeInterval)] = []
|
||||
|
||||
// Measure each view type
|
||||
let start1 = CFAbsoluteTimeGetCurrent()
|
||||
let start1 = Date()
|
||||
for _ in 0..<iterations {
|
||||
_ = renderToBuffer(VStack { Text("A"); Text("B") }, context: context)
|
||||
}
|
||||
results.append(("VStack (2 children)", CFAbsoluteTimeGetCurrent() - start1))
|
||||
results.append(("VStack (2 children)", Date().timeIntervalSince(start1)))
|
||||
|
||||
let start2 = CFAbsoluteTimeGetCurrent()
|
||||
let start2 = Date()
|
||||
for _ in 0..<iterations {
|
||||
_ = renderToBuffer(HStack { Text("A"); Text("B") }, context: context)
|
||||
}
|
||||
results.append(("HStack (2 children)", CFAbsoluteTimeGetCurrent() - start2))
|
||||
results.append(("HStack (2 children)", Date().timeIntervalSince(start2)))
|
||||
|
||||
let start3 = CFAbsoluteTimeGetCurrent()
|
||||
let start3 = Date()
|
||||
for _ in 0..<iterations {
|
||||
_ = renderToBuffer(Button("Test") {}, context: context)
|
||||
}
|
||||
results.append(("Button", CFAbsoluteTimeGetCurrent() - start3))
|
||||
results.append(("Button", Date().timeIntervalSince(start3)))
|
||||
|
||||
var isOn = false
|
||||
let start4 = CFAbsoluteTimeGetCurrent()
|
||||
let start4 = Date()
|
||||
for _ in 0..<iterations {
|
||||
_ = renderToBuffer(Toggle("Test", isOn: Binding(get: { isOn }, set: { isOn = $0 })), context: context)
|
||||
}
|
||||
results.append(("Toggle", CFAbsoluteTimeGetCurrent() - start4))
|
||||
results.append(("Toggle", Date().timeIntervalSince(start4)))
|
||||
|
||||
// Print results
|
||||
print("\n=== Render Performance Statistics ===")
|
||||
|
||||
@@ -17,11 +17,11 @@ import Testing
|
||||
struct StringPerformanceTests {
|
||||
|
||||
private func measure(_ name: String, iterations: Int = 1000, block: () -> Void) -> TimeInterval {
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
let start = Date()
|
||||
for _ in 0..<iterations {
|
||||
block()
|
||||
}
|
||||
let time = CFAbsoluteTimeGetCurrent() - start
|
||||
let time = Date().timeIntervalSince(start)
|
||||
let perIteration = (time / Double(iterations)) * 1000
|
||||
print(" \(name): \(String(format: "%.4f", perIteration))ms per iteration")
|
||||
return time
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
# TextField Component
|
||||
|
||||
## Preface
|
||||
|
||||
This plan implements TextField, an essential text input component for TUIKit. TextField provides an editable single-line text interface with cursor navigation, text editing, and SwiftUI-conformant API. Users can type, delete, and navigate within the field using standard keyboard controls. The component supports placeholder text (prompt), focus states with visual feedback, and the `.onSubmit()` modifier for form submission. This is a fundamental building block for forms, search fields, and any user text input.
|
||||
|
||||
## Context / Problem
|
||||
|
||||
TUIKit currently has no way for users to enter text. All existing controls (Button, Toggle, Menu, List) are selection-based, not input-based. TextField is essential for:
|
||||
|
||||
- Login/authentication forms (username, password)
|
||||
- Search functionality
|
||||
- Configuration dialogs
|
||||
- Any application requiring user text input
|
||||
|
||||
### SwiftUI TextField API
|
||||
|
||||
From the official documentation, TextField has these key signatures:
|
||||
|
||||
```swift
|
||||
// Basic string binding
|
||||
init(_ titleKey: LocalizedStringKey, text: Binding<String>)
|
||||
init(_ title: S, text: Binding<String>) where S: StringProtocol
|
||||
|
||||
// With prompt (placeholder)
|
||||
init(_ titleKey: LocalizedStringKey, text: Binding<String>, prompt: Text?)
|
||||
init(_ title: S, text: Binding<String>, prompt: Text?) where S: StringProtocol
|
||||
|
||||
// With ViewBuilder label
|
||||
init(text: Binding<String>, prompt: Text?, @ViewBuilder label: () -> Label)
|
||||
```
|
||||
|
||||
Key behaviors:
|
||||
- Updates the bound value **continuously** as the user types
|
||||
- `.onSubmit(of:_:)` modifier invokes action on Enter
|
||||
- `.focused($focusState)` for focus management
|
||||
- `.textFieldStyle(_:)` for styling (`.plain`, `.roundedBorder`, etc.)
|
||||
|
||||
## Specification / Goal
|
||||
|
||||
### Success Criteria
|
||||
|
||||
1. **SwiftUI API parity**: Match TextField signatures exactly
|
||||
2. **Text editing**: Insert, delete (backspace/delete), cursor movement
|
||||
3. **Cursor navigation**: Left/Right arrows, Home/End
|
||||
4. **Focus integration**: Register with FocusManager, visual focus indicator
|
||||
5. **Prompt/placeholder**: Show when text is empty and not focused
|
||||
6. **Submit action**: `.onSubmit()` modifier triggers on Enter
|
||||
7. **Disabled state**: Prevent editing when disabled
|
||||
8. **All tests pass**
|
||||
|
||||
### Terminal-Specific Considerations
|
||||
|
||||
- No mouse support (keyboard only)
|
||||
- Single-line only (no multi-line in v1)
|
||||
- No clipboard/paste support in v1 (terminal limitation)
|
||||
- Cursor rendered as block or underscore character
|
||||
|
||||
## Design
|
||||
|
||||
### Visual Rendering
|
||||
|
||||
```
|
||||
Unfocused, empty: [Enter username...] (prompt in dim)
|
||||
Unfocused, with text: [john.doe ] (text in normal)
|
||||
Focused, empty: [█ ] (cursor, brackets pulse)
|
||||
Focused, with text: [john.d█e ] (cursor in text, brackets pulse)
|
||||
Disabled: [john.doe ] (dim text and brackets)
|
||||
```
|
||||
|
||||
### Architecture
|
||||
|
||||
Following the established TUIKit pattern (like Toggle, Button):
|
||||
|
||||
```swift
|
||||
// Public View
|
||||
public struct TextField: View {
|
||||
let title: String
|
||||
let text: Binding<String>
|
||||
let prompt: Text?
|
||||
let focusID: String
|
||||
let isDisabled: Bool
|
||||
|
||||
public var body: some View {
|
||||
_TextFieldCore(...)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal Renderable
|
||||
private struct _TextFieldCore: View, Renderable {
|
||||
var body: Never { fatalError() }
|
||||
|
||||
func renderToBuffer(context: RenderContext) -> FrameBuffer {
|
||||
// Register TextFieldHandler with FocusManager
|
||||
// Render text with cursor position
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TextFieldHandler (Focusable)
|
||||
|
||||
New handler class for text field focus and key handling:
|
||||
|
||||
```swift
|
||||
final class TextFieldHandler: Focusable {
|
||||
let focusID: String
|
||||
var canBeFocused: Bool
|
||||
|
||||
// Text state
|
||||
var text: Binding<String>
|
||||
var cursorPosition: Int // Character index
|
||||
|
||||
// Callbacks
|
||||
var onSubmit: (() -> Void)?
|
||||
|
||||
func handleKeyEvent(_ event: KeyEvent) -> Bool {
|
||||
switch event.key {
|
||||
case .character(let char):
|
||||
insertCharacter(char)
|
||||
return true
|
||||
case .backspace:
|
||||
deleteBackward()
|
||||
return true
|
||||
case .delete:
|
||||
deleteForward()
|
||||
return true
|
||||
case .left:
|
||||
moveCursorLeft()
|
||||
return true
|
||||
case .right:
|
||||
moveCursorRight()
|
||||
return true
|
||||
case .home:
|
||||
cursorPosition = 0
|
||||
return true
|
||||
case .end:
|
||||
cursorPosition = text.wrappedValue.count
|
||||
return true
|
||||
case .enter:
|
||||
onSubmit?()
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State Persistence
|
||||
|
||||
Use `StateStorage` to persist cursor position between renders:
|
||||
|
||||
```swift
|
||||
struct TextFieldState {
|
||||
var cursorPosition: Int
|
||||
}
|
||||
```
|
||||
|
||||
### Modifier Support
|
||||
|
||||
```swift
|
||||
// onSubmit modifier (already exists in TUIKit?)
|
||||
TextField("Username", text: $username)
|
||||
.onSubmit {
|
||||
login()
|
||||
}
|
||||
|
||||
// textFieldStyle modifier
|
||||
TextField("Search", text: $query)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core TextField
|
||||
|
||||
1. Create `TextFieldHandler` in `Sources/TUIkit/Focus/`
|
||||
2. Create `TextField.swift` in `Sources/TUIkit/Views/`
|
||||
3. Implement basic rendering (text + cursor)
|
||||
4. Implement character input
|
||||
5. Implement backspace/delete
|
||||
6. Implement cursor movement (left/right/home/end)
|
||||
7. Add focus indicator (pulsing brackets)
|
||||
|
||||
### Phase 2: SwiftUI API Parity
|
||||
|
||||
1. Add `init(_:text:)` - basic
|
||||
2. Add `init(_:text:prompt:)` - with placeholder
|
||||
3. Add `init(text:prompt:label:)` - ViewBuilder label
|
||||
4. Implement prompt/placeholder rendering
|
||||
|
||||
### Phase 3: Modifiers & Polish
|
||||
|
||||
1. Implement `.onSubmit()` support
|
||||
2. Implement `.textFieldStyle(_:)` modifier
|
||||
3. Add `.disabled()` support
|
||||
4. Add width constraint support
|
||||
|
||||
### Phase 4: Testing
|
||||
|
||||
1. Unit tests for TextFieldHandler
|
||||
2. Rendering tests for TextField
|
||||
3. Focus integration tests
|
||||
4. Example app integration
|
||||
|
||||
## Checklist
|
||||
|
||||
### Phase 1: Core TextField
|
||||
- [ ] Create TextFieldHandler class
|
||||
- [ ] Create TextField struct with body: some View
|
||||
- [ ] Implement _TextFieldCore with Renderable
|
||||
- [ ] Render text content with cursor
|
||||
- [ ] Handle character input
|
||||
- [ ] Handle backspace/delete
|
||||
- [ ] Handle cursor movement (left/right)
|
||||
- [ ] Handle home/end keys
|
||||
- [ ] Add focus indicator (pulsing brackets)
|
||||
- [ ] State persistence for cursor position
|
||||
|
||||
### Phase 2: SwiftUI API Parity
|
||||
- [ ] init(_:text:) - basic initializer
|
||||
- [ ] init(_:text:prompt:) - with placeholder
|
||||
- [ ] init(text:prompt:label:) - ViewBuilder label
|
||||
- [ ] Render prompt when empty and unfocused
|
||||
|
||||
### Phase 3: Modifiers & Polish
|
||||
- [ ] .onSubmit() modifier support
|
||||
- [ ] .textFieldStyle(_:) modifier
|
||||
- [ ] TextFieldStyle protocol
|
||||
- [ ] PlainTextFieldStyle
|
||||
- [ ] RoundedBorderTextFieldStyle (default for TUI)
|
||||
- [ ] .disabled() support
|
||||
- [ ] Width/frame support
|
||||
|
||||
### Phase 4: Testing
|
||||
- [ ] TextFieldHandler key event tests
|
||||
- [ ] TextField rendering tests
|
||||
- [ ] Focus integration tests
|
||||
- [ ] Prompt/placeholder tests
|
||||
- [ ] Style tests
|
||||
- [ ] Example app demo page
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Cursor representation**: Block (`█`), underscore (`_`), or pipe (`|`)?
|
||||
- Recommendation: Block when focused, hidden when unfocused
|
||||
|
||||
2. **Max length**: Should TextField support a character limit?
|
||||
- Can add later as `.limit(_:)` modifier
|
||||
|
||||
3. **Secure field**: Should we implement SecureField (password) in this plan or separately?
|
||||
- Recommendation: Separate plan, but design TextField to support masking
|
||||
|
||||
4. **Selection**: Should we support text selection (shift+arrows)?
|
||||
- v1: No, keep simple. Add in future version.
|
||||
|
||||
## Files
|
||||
|
||||
### New Files
|
||||
- `Sources/TUIkit/Focus/TextFieldHandler.swift`
|
||||
- `Sources/TUIkit/Views/TextField.swift`
|
||||
- `Sources/TUIkit/Modifiers/TextFieldStyleModifier.swift`
|
||||
- `Tests/TUIkitTests/TextFieldTests.swift`
|
||||
- `Tests/TUIkitTests/TextFieldHandlerTests.swift`
|
||||
|
||||
### Modified Files
|
||||
- `Sources/TUIkitExample/Pages/` - Add TextField demo page
|
||||
|
||||
## Dependencies
|
||||
|
||||
- FocusManager (for focus registration)
|
||||
- StateStorage (for cursor position persistence)
|
||||
- ActionHandler pattern (reference implementation)
|
||||
- Existing View/Renderable architecture
|
||||
Reference in New Issue
Block a user