- 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
143 lines
4.0 KiB
Swift
143 lines
4.0 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Text Control
|
|
|
|
struct TextControl: View {
|
|
let schema: ControlSchema
|
|
@ObservedObject var engine: BricksEngine
|
|
|
|
var body: some View {
|
|
let text = displayText
|
|
Text(text)
|
|
.font(resolveFont())
|
|
.foregroundColor(resolveColor())
|
|
.lineLimit(nil)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
private var displayText: String {
|
|
let raw = schema.options.otext ?? schema.options.text ?? ""
|
|
if schema.options.i18n != false {
|
|
return engine.i18n.t(raw)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
private func resolveFont() -> Font {
|
|
if let fs = schema.options.cfontsize {
|
|
return .system(size: CGFloat(fs * 14))
|
|
}
|
|
if let fs = schema.options.fontsize, let size = Double(fs.replacingOccurrences(of: "px", with: "")) {
|
|
return .system(size: CGFloat(size))
|
|
}
|
|
return .body
|
|
}
|
|
|
|
private func resolveColor() -> Color {
|
|
if let color = schema.options.color {
|
|
return Color.fromCSS(color)
|
|
}
|
|
return .primary
|
|
}
|
|
}
|
|
|
|
// MARK: - Title Control
|
|
|
|
struct TitleControl: View {
|
|
let schema: ControlSchema
|
|
@ObservedObject var engine: BricksEngine
|
|
let level: Int
|
|
|
|
var body: some View {
|
|
let text = schema.options.text ?? schema.options.otext ?? ""
|
|
let translated = (schema.options.i18n != false) ? engine.i18n.t(text) : text
|
|
|
|
Group {
|
|
switch level {
|
|
case 1: Text(translated).font(.largeTitle).bold()
|
|
case 2: Text(translated).font(.title).bold()
|
|
case 3: Text(translated).font(.title2).bold()
|
|
case 4: Text(translated).font(.title3).bold()
|
|
case 5: Text(translated).font(.headline)
|
|
case 6: Text(translated).font(.subheadline).bold()
|
|
default: Text(translated).font(.body)
|
|
}
|
|
}
|
|
.foregroundColor(resolveColor())
|
|
}
|
|
|
|
private func resolveColor() -> Color {
|
|
if let color = schema.options.color {
|
|
return Color.fromCSS(color)
|
|
}
|
|
return .primary
|
|
}
|
|
}
|
|
|
|
// MARK: - Label Control
|
|
|
|
struct LabelControl: View {
|
|
let schema: ControlSchema
|
|
@ObservedObject var engine: BricksEngine
|
|
|
|
var body: some View {
|
|
let text = schema.options.text ?? schema.options.otext ?? ""
|
|
let translated = (schema.options.i18n != false) ? engine.i18n.t(text) : text
|
|
|
|
HStack(spacing: 4) {
|
|
if let icon = schema.options.icon {
|
|
Text(icon)
|
|
}
|
|
Text(translated)
|
|
.font(.body)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Color Extension
|
|
|
|
extension Color {
|
|
static func fromCSS(_ css: String) -> Color {
|
|
let cleaned = css.trimmingCharacters(in: .whitespaces)
|
|
|
|
if cleaned.hasPrefix("#") {
|
|
return Color(hex: cleaned)
|
|
}
|
|
|
|
if cleaned.hasPrefix("var(") {
|
|
// CSS变量 → 使用默认色
|
|
return .primary
|
|
}
|
|
|
|
switch cleaned.lowercased() {
|
|
case "red": return .red
|
|
case "blue": return .blue
|
|
case "green": return .green
|
|
case "yellow": return .yellow
|
|
case "orange": return .orange
|
|
case "purple": return .purple
|
|
case "pink": return .pink
|
|
case "white": return .white
|
|
case "black": return .black
|
|
case "gray", "grey": return .gray
|
|
case "transparent": return .clear
|
|
default: return .primary
|
|
}
|
|
}
|
|
|
|
init(hex: String) {
|
|
var hexStr = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if hexStr.hasPrefix("#") { hexStr.removeFirst() }
|
|
|
|
var rgb: UInt64 = 0
|
|
Scanner(string: hexStr).scanHexInt64(&rgb)
|
|
|
|
let r = Double((rgb >> 16) & 0xFF) / 255.0
|
|
let g = Double((rgb >> 8) & 0xFF) / 255.0
|
|
let b = Double(rgb & 0xFF) / 255.0
|
|
|
|
self.init(red: r, green: g, blue: b)
|
|
}
|
|
}
|