mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Merge pull request #28 from phranck/refactor/eliminate-environmentstorage-singleton
Refactor: Eliminate EnvironmentStorage singleton (last one)
This commit is contained in:
@@ -82,7 +82,7 @@ internal final class AppRunner<A: App> {
|
||||
items: PaletteRegistry.all,
|
||||
applyToEnvironment: { item in
|
||||
if let palette = item as? any Palette {
|
||||
EnvironmentStorage.shared.environment.palette = palette
|
||||
EnvironmentStorage.active.environment.palette = palette
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -90,7 +90,7 @@ internal final class AppRunner<A: App> {
|
||||
items: AppearanceRegistry.all,
|
||||
applyToEnvironment: { item in
|
||||
if let appearance = item as? Appearance {
|
||||
EnvironmentStorage.shared.environment.appearance = appearance
|
||||
EnvironmentStorage.active.environment.appearance = appearance
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -128,7 +128,7 @@ internal final class AppRunner<A: App> {
|
||||
terminal.enableRawMode()
|
||||
|
||||
// Set up environment with all managed subsystems
|
||||
EnvironmentStorage.shared.environment = renderer.buildEnvironment()
|
||||
EnvironmentStorage.active.environment = renderer.buildEnvironment()
|
||||
|
||||
// Register for state changes
|
||||
AppState.active.observe { [signals] in
|
||||
@@ -169,7 +169,7 @@ internal final class AppRunner<A: App> {
|
||||
terminal.showCursor()
|
||||
terminal.exitAlternateScreen()
|
||||
AppState.active.clearObservers()
|
||||
EnvironmentStorage.shared.reset()
|
||||
EnvironmentStorage.active.reset()
|
||||
focusManager.clear()
|
||||
tuiContext.reset()
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ internal struct RenderLoop<A: App> {
|
||||
)
|
||||
|
||||
// Update global environment storage
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
|
||||
// Render main content (background fill happens in renderScene)
|
||||
let scene = app.body
|
||||
|
||||
@@ -221,7 +221,7 @@ private struct AppearanceManagerKey: EnvironmentKey {
|
||||
items: AppearanceRegistry.all,
|
||||
applyToEnvironment: { item in
|
||||
if let appearance = item as? Appearance {
|
||||
EnvironmentStorage.shared.environment.appearance = appearance
|
||||
EnvironmentStorage.active.environment.appearance = appearance
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -85,9 +85,17 @@ public struct EnvironmentValues: @unchecked Sendable {
|
||||
///
|
||||
/// This allows views to access environment values without explicit passing.
|
||||
/// The environment is set by the rendering system before rendering each view.
|
||||
///
|
||||
/// `AppRunner` creates and manages the active instance. Property wrappers
|
||||
/// like ``Environment`` and view modifiers like ``EnvironmentModifier``
|
||||
/// access it through ``active``.
|
||||
public final class EnvironmentStorage: @unchecked Sendable {
|
||||
/// The shared environment storage.
|
||||
public static let shared = EnvironmentStorage()
|
||||
/// The active environment storage for the current application.
|
||||
///
|
||||
/// Set by `AppRunner` during initialization. The ``Environment``
|
||||
/// property wrapper, ``FocusState``, and ``EnvironmentModifier``
|
||||
/// all read and write through this property.
|
||||
public nonisolated(unsafe) static var active = EnvironmentStorage()
|
||||
|
||||
/// Lock protecting all mutable state.
|
||||
private let lock = NSLock()
|
||||
@@ -98,7 +106,8 @@ public final class EnvironmentStorage: @unchecked Sendable {
|
||||
/// Stack of environments for nested rendering.
|
||||
private var stack: [EnvironmentValues] = []
|
||||
|
||||
private init() {}
|
||||
/// Creates a new environment storage instance.
|
||||
public init() {}
|
||||
|
||||
/// The current environment values.
|
||||
public var environment: EnvironmentValues {
|
||||
@@ -189,7 +198,7 @@ public struct Environment<Value>: @unchecked Sendable {
|
||||
|
||||
/// The current value from the environment.
|
||||
public var wrappedValue: Value {
|
||||
EnvironmentStorage.shared.environment[keyPath: keyPath]
|
||||
EnvironmentStorage.active.environment[keyPath: keyPath]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +234,7 @@ extension EnvironmentModifier: Renderable {
|
||||
let modifiedContext = context.withEnvironment(modifiedEnvironment)
|
||||
|
||||
// Render content with modified environment
|
||||
return EnvironmentStorage.shared.withEnvironment(modifiedEnvironment) {
|
||||
return EnvironmentStorage.active.withEnvironment(modifiedEnvironment) {
|
||||
TUIkit.renderToBuffer(content, context: modifiedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,13 +287,13 @@ public class FocusState {
|
||||
///
|
||||
/// Reads from the current environment's focus manager.
|
||||
public var isFocused: Bool {
|
||||
EnvironmentStorage.shared.environment.focusManager.isFocused(id: id)
|
||||
EnvironmentStorage.active.environment.focusManager.isFocused(id: id)
|
||||
}
|
||||
|
||||
/// Requests focus for this element.
|
||||
///
|
||||
/// Uses the current environment's focus manager.
|
||||
public func requestFocus() {
|
||||
EnvironmentStorage.shared.environment.focusManager.focus(id: id)
|
||||
EnvironmentStorage.active.environment.focusManager.focus(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ extension EnvironmentValues {
|
||||
extension Color {
|
||||
/// Access palette colors from the current environment.
|
||||
///
|
||||
/// These colors read from `EnvironmentStorage.shared` during rendering.
|
||||
/// These colors read from `EnvironmentStorage.active` during rendering.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -204,7 +204,7 @@ extension Color {
|
||||
public enum PaletteColors {
|
||||
/// Gets the current palette from environment storage.
|
||||
private static var current: any Palette {
|
||||
EnvironmentStorage.shared.environment.palette
|
||||
EnvironmentStorage.active.environment.palette
|
||||
}
|
||||
|
||||
/// Primary background color.
|
||||
@@ -735,7 +735,7 @@ private struct PaletteManagerKey: EnvironmentKey {
|
||||
items: PaletteRegistry.all,
|
||||
applyToEnvironment: { item in
|
||||
if let palette = item as? any Palette {
|
||||
EnvironmentStorage.shared.environment.palette = palette
|
||||
EnvironmentStorage.active.environment.palette = palette
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ public protocol Cyclable: Sendable {
|
||||
/// # Environment Integration
|
||||
///
|
||||
/// On every change the manager writes the current item into
|
||||
/// `EnvironmentStorage.shared` via the closure provided at init,
|
||||
/// `EnvironmentStorage.active` via the closure provided at init,
|
||||
/// then triggers a re-render through `AppState.active.setNeedsRender()`.
|
||||
public final class ThemeManager: @unchecked Sendable {
|
||||
/// The current item index.
|
||||
@@ -78,7 +78,7 @@ public final class ThemeManager: @unchecked Sendable {
|
||||
/// - Parameters:
|
||||
/// - items: The items to cycle through. Must not be empty.
|
||||
/// - applyToEnvironment: A closure that writes the current item
|
||||
/// into `EnvironmentStorage.shared.environment`.
|
||||
/// into `EnvironmentStorage.active.environment`.
|
||||
public init(items: [any Cyclable], applyToEnvironment: @escaping @Sendable (any Cyclable) -> Void) {
|
||||
precondition(!items.isEmpty, "ThemeManager requires at least one item")
|
||||
self.items = items
|
||||
|
||||
@@ -16,7 +16,7 @@ private func createTestContext(width: Int = 80, height: Int = 24) -> RenderConte
|
||||
let focusManager = FocusManager()
|
||||
var environment = EnvironmentValues()
|
||||
environment.focusManager = focusManager
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
|
||||
return RenderContext(
|
||||
availableWidth: width,
|
||||
@@ -27,7 +27,7 @@ private func createTestContext(width: Int = 80, height: Int = 24) -> RenderConte
|
||||
|
||||
/// Cleans up the environment after a test.
|
||||
private func cleanupEnvironment() {
|
||||
EnvironmentStorage.shared.reset()
|
||||
EnvironmentStorage.active.reset()
|
||||
}
|
||||
|
||||
// MARK: - Button Style Tests
|
||||
|
||||
@@ -85,7 +85,7 @@ struct EnvironmentStorageTests {
|
||||
|
||||
@Test("push and pop restore previous environment")
|
||||
func pushPop() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
let original = storage.environment
|
||||
|
||||
@@ -100,7 +100,7 @@ struct EnvironmentStorageTests {
|
||||
|
||||
@Test("Nested push/pop restores correctly")
|
||||
func nestedPushPop() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
|
||||
var first = EnvironmentValues()
|
||||
@@ -120,7 +120,7 @@ struct EnvironmentStorageTests {
|
||||
|
||||
@Test("Pop on empty stack is safe")
|
||||
func popEmptyStack() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
// Should not crash
|
||||
storage.pop()
|
||||
@@ -128,7 +128,7 @@ struct EnvironmentStorageTests {
|
||||
|
||||
@Test("withEnvironment scopes environment correctly")
|
||||
func withEnvironment() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
|
||||
var scoped = EnvironmentValues()
|
||||
@@ -143,7 +143,7 @@ struct EnvironmentStorageTests {
|
||||
|
||||
@Test("reset clears all state")
|
||||
func reset() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
var modified = EnvironmentValues()
|
||||
modified[TestStringKey.self] = "dirty"
|
||||
storage.push(modified)
|
||||
@@ -160,7 +160,7 @@ struct EnvironmentPropertyWrapperTests {
|
||||
|
||||
@Test("@Environment reads current value from shared storage")
|
||||
func readsFromStorage() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
|
||||
var env = storage.environment
|
||||
@@ -176,7 +176,7 @@ struct EnvironmentPropertyWrapperTests {
|
||||
|
||||
@Test("@Environment reflects changes after storage update")
|
||||
func reflectsChanges() {
|
||||
let storage = EnvironmentStorage.shared
|
||||
let storage = EnvironmentStorage.active
|
||||
storage.reset()
|
||||
|
||||
let wrapper = Environment(\.testString)
|
||||
|
||||
@@ -345,7 +345,7 @@ struct FocusStateTests {
|
||||
let manager = FocusManager()
|
||||
var environment = EnvironmentValues()
|
||||
environment.focusManager = manager
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
|
||||
let state = FocusState(id: "state-test")
|
||||
let element = MockFocusable(id: "state-test")
|
||||
@@ -356,7 +356,7 @@ struct FocusStateTests {
|
||||
#expect(state.isFocused)
|
||||
|
||||
// Cleanup
|
||||
EnvironmentStorage.shared.reset()
|
||||
EnvironmentStorage.active.reset()
|
||||
}
|
||||
|
||||
@Test("FocusState requestFocus works")
|
||||
@@ -365,7 +365,7 @@ struct FocusStateTests {
|
||||
let manager = FocusManager()
|
||||
var environment = EnvironmentValues()
|
||||
environment.focusManager = manager
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
|
||||
let element1 = MockFocusable(id: "req-1")
|
||||
let element2 = MockFocusable(id: "req-2")
|
||||
@@ -379,7 +379,7 @@ struct FocusStateTests {
|
||||
#expect(manager.isFocused(id: "req-2"))
|
||||
|
||||
// Cleanup
|
||||
EnvironmentStorage.shared.reset()
|
||||
EnvironmentStorage.active.reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -923,7 +923,7 @@ struct StatusBarItemsModifierTests {
|
||||
environment: environment
|
||||
)
|
||||
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
_ = renderToBuffer(view, context: context)
|
||||
|
||||
// Check that user items were set
|
||||
@@ -957,7 +957,7 @@ struct StatusBarItemsModifierTests {
|
||||
environment: environment
|
||||
)
|
||||
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
_ = renderToBuffer(view, context: context)
|
||||
|
||||
// Context items should be active
|
||||
@@ -989,7 +989,7 @@ struct StatusBarItemsModifierTests {
|
||||
environment: environment
|
||||
)
|
||||
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
let buffer = renderToBuffer(view, context: context)
|
||||
|
||||
// Content should be rendered
|
||||
@@ -1036,7 +1036,7 @@ struct StatusBarItemsModifierTests {
|
||||
environment: environment
|
||||
)
|
||||
|
||||
EnvironmentStorage.shared.environment = environment
|
||||
EnvironmentStorage.active.environment = environment
|
||||
_ = renderToBuffer(outerView, context: context)
|
||||
|
||||
// Inner context should be on top
|
||||
|
||||
Reference in New Issue
Block a user