swiftbricks/Sources/SwiftBricks/Renderer/ControlRenderer.swift
Hermes Agent 5ba2ae163b feat: ControlRenderer checks widget registry for custom widgets
Added registry lookup at top of renderWidget() before hardcoded switch.
Custom widgets registered via engine.registerWidget(type:factory) are
now rendered by the factory closure, enabling app-level extensions.
2026-06-21 17:48:06 +08:00

171 lines
5.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
/// JSON schemaSwiftUI
/// SwiftBricksBrickswidget
public struct ControlRenderer: View {
let schema: ControlSchema
@ObservedObject var engine: BricksEngine
public init(schema: ControlSchema, engine: BricksEngine) {
self.schema = schema
self.engine = engine
}
public var body: some View {
renderWidget(schema)
.frame(
maxWidth: resolveWidth(schema.options),
maxHeight: resolveHeight(schema.options),
alignment: resolveAlignment(schema.options)
)
}
// MARK: - Widget
@ViewBuilder
func renderWidget(_ schema: ControlSchema) -> some View {
let type = schema.widgettype
// widget
if let factory = engine.getWidgetFactory(type: type) {
factory(schema, engine)
} else {
renderKnownWidget(schema, type: type)
}
}
@ViewBuilder
func renderKnownWidget(_ schema: ControlSchema, type: String) -> some View {
switch type {
//
case "Text":
TextControl(schema: schema, engine: engine)
case "Title1":
TitleControl(schema: schema, engine: engine, level: 1)
case "Title2":
TitleControl(schema: schema, engine: engine, level: 2)
case "Title3":
TitleControl(schema: schema, engine: engine, level: 3)
case "Title4":
TitleControl(schema: schema, engine: engine, level: 4)
case "Title5":
TitleControl(schema: schema, engine: engine, level: 5)
case "Title6":
TitleControl(schema: schema, engine: engine, level: 6)
case "Label":
LabelControl(schema: schema, engine: engine)
//
case "Input", "UiStr":
InputControl(schema: schema, engine: engine)
case "Textarea", "UiText":
TextareaControl(schema: schema, engine: engine)
case "UiNumber":
NumberControl(schema: schema, engine: engine)
case "UiDate":
DateControl(schema: schema, engine: engine)
case "Select", "UiCode":
SelectControl(schema: schema, engine: engine)
//
case "Button":
ButtonControl(schema: schema, engine: engine)
//
case "VBox", "Card":
VBoxControl(schema: schema, engine: engine)
case "HBox":
HBoxControl(schema: schema, engine: engine)
case "VScrollPanel", "HScrollPanel":
ScrollPanelControl(schema: schema, engine: engine)
case "Filler":
Spacer().frame(maxWidth: .infinity, maxHeight: .infinity)
case "DynamicColumn":
DynamicColumnControl(schema: schema, engine: engine)
//
case "Tabular", "DataViewer":
TabularControl(schema: schema, engine: engine)
case "Form":
FormControl(schema: schema, engine: engine)
case "InlineForm":
InlineFormControl(schema: schema, engine: engine)
//
case "TabView":
TabViewControl(schema: schema, engine: engine)
case "Menu":
MenuControl(schema: schema, engine: engine)
//
case "PopupWindow":
PopupWindowControl(schema: schema, engine: engine)
// HTML
case "Html":
HtmlControl(schema: schema, engine: engine)
// Image
case "Image":
ImageControl(schema: schema, engine: engine)
// urlwidget
case "urlwidget":
UrlWidgetControl(schema: schema, engine: engine)
default:
// widgettype
VStack {
Text("⚠️ Unknown: \(type)")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
// MARK: -
private func resolveWidth(_ opts: ControlOptions) -> CGFloat? {
if let w = opts.width {
if w == "100%" { return nil } // 使framemaxWidth
if let px = parsePixels(w) { return px }
}
return nil
}
private func resolveHeight(_ opts: ControlOptions) -> CGFloat? {
if let h = opts.height {
if h == "100%" { return nil }
if let px = parsePixels(h) { return px }
}
return nil
}
private func resolveAlignment(_ opts: ControlOptions) -> Alignment {
switch opts.alignItems {
case "center": return .center
case "flex-end", "end": return .trailing
case "flex-start", "start": return .leading
default: return .topLeading
}
}
private func parsePixels(_ str: String) -> CGFloat? {
let cleaned = str.replacingOccurrences(of: "px", with: "")
return CGFloat(Double(cleaned) ?? 0)
}
}
// MARK: -
struct SubWidgetsView: View {
let subwidgets: [ControlSchema]
@ObservedObject var engine: BricksEngine
var body: some View {
ForEach(Array(subwidgets.enumerated()), id: \.offset) { _, sub in
ControlRenderer(schema: sub, engine: engine)
}
}
}