Adds SwiftUI-style @Environment property wrapper with key-path and object-injection flavors, an @Observable bridge that flushes on a GLib idle source at highIdle priority (ahead of GTK redraw), targeted widget updates driven by Observation tracking, and two-way observable bindings that ride the existing bindProperty path. New library files (4): - State/ObservationBridge.swift -- coalesced flush bridge - State/EnvironmentValues.swift -- scope-local key-to-box map - State/Environment.swift -- @Environment wrapper, DynamicProperty - Core/View+Environment.swift -- .environment(...) modifiers Modified library files (8): - DependencyTracker: import Observation, observing: param, didArmObservation - EitherView/ForEach: opt container trackers out of Observation - NodeRegistry: isEmpty for retention check - PropertyBinding: hasDependencies || didArmObservation gate - Binding: readOnly init, observable key-path init - MountContext: environment field, withEnvironment, inheritance - AnyView: _resolveDynamicProperties hook before user body eval New tests (21 across 3 files): - EnvironmentTests (7): scoping, nesting, projections, ForEach, lazy branches, reuse - ObservableTests (11): acceptance case, write-back, coalescing, teardown, binding echo, re-arm de-dupe, retention, flush ordering - StateTests (+3): Observation arming unit tests Example: EnvironmentDemoPage demonstrates the feature end to end.
107 lines
3.2 KiB
Swift
107 lines
3.2 KiB
Swift
import Portico
|
|
|
|
@main
|
|
struct ExampleApp: App {
|
|
var applicationId: String? { "dev.bscubed.PorticoExample" }
|
|
|
|
@State private var count = 0
|
|
@State private var name = "Portico"
|
|
@State private var darkMode = false
|
|
@State private var volume = 75.0
|
|
@State private var currentPage: UInt32 = 0
|
|
@State private var uptime = 0
|
|
|
|
var body: some Scene {
|
|
ApplicationWindow { _ in
|
|
VStack {
|
|
HeaderBar()
|
|
|
|
Carousel {
|
|
introPage
|
|
counterPage
|
|
settingsPage
|
|
AsyncDemoPage()
|
|
EnvironmentDemoPage()
|
|
}
|
|
.onPageChanged { page in currentPage = page }
|
|
|
|
Label("Uptime: \(uptime)s, Page \(currentPage + 1) of 5")
|
|
.dimmed()
|
|
.halign(.center)
|
|
.margin(8)
|
|
.task(every: .seconds(1)) { uptime += 1 }
|
|
}
|
|
}
|
|
.title("Portico Demo")
|
|
.defaultSize(width: 1080, height: 720)
|
|
}
|
|
|
|
var introPage: some View {
|
|
Clamp {
|
|
StatusPage {
|
|
VStack(spacing: 6) {
|
|
Label("Declarative GTK apps in Swift")
|
|
.title3()
|
|
.marginTop(16)
|
|
Label("A familiar SwiftUI-like API, zero GObject boilerplate")
|
|
.marginBottom(16)
|
|
}
|
|
.card()
|
|
}
|
|
.title("Welcome to Portico")
|
|
.description("Swipe to explore")
|
|
.iconName("face-smile-symbolic")
|
|
}
|
|
.hexpand(true)
|
|
.vexpand(true)
|
|
}
|
|
|
|
var counterPage: some View {
|
|
Clamp {
|
|
StatusPage {
|
|
HStack(spacing: 12) {
|
|
Button("-") { count -= 1 }
|
|
.circular()
|
|
Label("Count: \(count)")
|
|
.title1()
|
|
Button("+") { count += 1 }
|
|
.circular()
|
|
}
|
|
.halign(.center)
|
|
}
|
|
.title("Reactive Counter")
|
|
.description("@State + Binding drive targeted widget updates")
|
|
.iconName("applications-engineering-symbolic")
|
|
}
|
|
.hexpand(true)
|
|
.vexpand(true)
|
|
}
|
|
|
|
var settingsPage: some View {
|
|
Clamp {
|
|
StatusPage {
|
|
VStack(spacing: 12) {
|
|
Label("Name: \(name), Dark mode: \(darkMode), Volume: \(volume)")
|
|
.title2()
|
|
ListBox {
|
|
EntryRow()
|
|
.title("Your Name")
|
|
.text($name)
|
|
SwitchRow()
|
|
.title("Dark Mode")
|
|
.active($darkMode)
|
|
SpinRow(min: 0, max: 100, step: 5)
|
|
.title("Volume")
|
|
.value($volume)
|
|
}
|
|
.boxedList()
|
|
}
|
|
}
|
|
.title("Quick Settings")
|
|
.description("Live GObject property bindings")
|
|
.iconName("application-x-firmware-symbolic")
|
|
}
|
|
.hexpand(true)
|
|
.vexpand(true)
|
|
}
|
|
}
|