// // ClassLike.swift // Adwaita // // Created by david-swift on 14.02.24. // /// Classes and interfaces share a lot of their properties. protocol ClassLike { /// The type name. var name: String { get } /// The C symbol prefix. var cSymbolPrefix: String { get } /// The C type. var cType: String? { get } /// The signals. var signals: [Signal] { get } /// The properties. var properties: [Property] { get } } extension ClassLike { /// Get the properties of the class and its parent classes. /// - Parameters: /// - namespace: The namespace. /// - configurations: The configurations for the classes in the namespace. /// - Returns: The properties. func properties(namespace: Namespace, configurations: [WidgetConfiguration]) -> [Property] { var properties = properties.filter { !($0.deprecated ?? false) } for type in parentTypes(namespace: namespace) { properties += type .properties(namespace: namespace, configurations: configurations) .map { property in var property = property if property.prefix == nil { property.prefix = type.prefix() property.cast = configurations.first { $0.class == type.name }?.cast ?? false } return property } } return properties.reduce(into: [String: Property]()) { result, property in result[property.name] = property } .values .sorted { $0.name < $1.name } } /// Get the code for the properties of the static widgets. /// - Parameters: /// - namespace: The namespace. /// - configs: The configurations. /// - Returns: The code. func staticWidgetProperties(namespace: Namespace, configs: [WidgetConfiguration]) -> String { var content = "" for type in parentTypes(namespace: namespace) { content += type.staticWidgetProperties(namespace: namespace, configs: configs) } guard let config = configs.first(where: { $0.class == name }) else { return content } for staticWidget in config.staticWidgets { content += """ /// The body for the widget "\(staticWidget.name)". var \(staticWidget.name): () -> Body = { [] } """ } return content } /// Get the assignments for the static widgets of the class and its parent classes. /// - Parameters: /// - namespace: The namespace. /// - configs: The configurations. /// - Returns: The code. func staticWidgets(namespace: Namespace, configs: [WidgetConfiguration]) -> String { var content = "" for type in parentTypes(namespace: namespace) { content += type.staticWidgets(namespace: namespace, configs: configs) } guard let config = configs.first(where: { $0.class == name }) else { return content } let widgetPointer = config.cast ? "storage.opaquePointer?.cast()" : "storage.opaquePointer" for widget in config.staticWidgets { content += """ var \(widget.name)Storage: [ViewStorage] = [] for view in \(widget.name)() { \(widget.name)Storage.append(view.storage(data: data, type: type)) \(widget.add)(\(widgetPointer), \(widget.name)Storage.last?.opaquePointer?.cast()) } storage.content["\(widget.name)"] = \(widget.name)Storage """ } return content } /// Generate the modifiers for static widgets. /// - Parameters: /// - config: The widget configuration. /// - configs: The available widget configurations. /// - namespace: The namespace. /// - Returns: The code. func generateWidgetModifiers( config: WidgetConfiguration, configs: [WidgetConfiguration], namespace: Namespace ) -> String { var content = "" for widget in config.staticWidgets { content += """ /// Set the body for "\(widget.name)". /// - Parameter body: The body. /// - Returns: The widget. public func \(widget.name)(@ViewBuilder _ body: @escaping () -> Body) -> Self { var newSelf = self newSelf.\(widget.name) = body return newSelf } """ } for type in parentTypes(namespace: namespace) { if let config = configs.first(where: { $0.class == type.name }) { content += type.generateWidgetModifiers(config: config, configs: configs, namespace: namespace) } } return content } /// Get the signals of the class and its parent classes. /// - Parameter namespace: The namespace. /// - Returns: The signals. func signals(namespace: Namespace) -> [Signal] { var signals: [Signal] = signals for type in parentTypes(namespace: namespace) { signals += type.signals(namespace: namespace) } return signals.reduce(into: [String: Signal]()) { result, property in result[property.name] = property } .values .sorted { $0.name < $1.name } } /// Get the parent class and interfaces. /// - Parameter namespace: The namespace in the namespace. /// - Returns: The types. func parentTypes(namespace: Namespace) -> [ClassLike] { var types: [ClassLike] = [] guard let `class` = self as? Class else { return types } if let parent = namespace.classes.first(where: { $0.name == `class`.parent }), parent.name != "Widget" { types.append(parent) } for conformance in `class`.conformances { if let type = namespace.interfaces.first(where: { $0.name == conformance.name }) { types.append(type) } } return types } /// Get the widget's prefix (e.g. "adw_preferences_row"). /// - Returns: The prefix. func prefix() -> String { if cType?.hasPrefix("Adw") ?? false { return "adw_\(cSymbolPrefix)" } else { return "gtk_\(cSymbolPrefix)" } } }