swiftbricks/Sources/SwiftBricks/Renderer/ControlRenderer.swift
Hermes Agent 36967370fd SwiftBricks: Bricks JSON UI framework native Swift/SwiftUI implementation
- Core: Schema (Codable), Store, EventBus, RPC, Engine, I18n
- Controls: Text, Title1-6, Label, Input, Textarea, Number, Date, Button, Select
  VBox, HBox, ScrollPanel, DynamicColumn, Tabular, Form, InlineForm,
  TabView, Menu, PopupWindow, Html, Image, UrlWidget
- Renderer: BricksView (main entry), ControlRenderer (recursive dispatch)
- 30+ widget types, 9 bind actiontypes
- Form validation (required/minlength/maxlength/min/max/email/number/pattern)
- i18n with multi-language JSON files
- 18 tests covering schema parsing, store, i18n, events
2026-06-21 12:12:03 +08:00

161 lines
5.1 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
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)
}
}
}