From ad16aa4badc5283a18fc8127e01d4eaf08bb4c26 Mon Sep 17 00:00:00 2001 From: stackotter Date: Sat, 4 Nov 2023 13:25:48 +1000 Subject: [PATCH] Simplify the AppBackend API to reduce the number of methods and improve consistency Before there were a lot of atomic methods, but SwiftCrossUI never needs to call them separately, so it's simpler to combine them into a single 'update' method for each widget type --- Sources/AppKitBackend/AppKitBackend.swift | 32 +- Sources/CursesBackend/CursesBackend.swift | 22 +- Sources/GtkBackend/GtkBackend.swift | 515 ++++++++--------- Sources/LVGLBackend/LVGLBackend.swift | 30 +- Sources/QtBackend/QtBackend.swift | 105 ++-- Sources/SwiftCrossUI/Backend/AppBackend.swift | 529 +++++++++--------- .../Modifiers/ForegroundColor.swift | 7 +- Sources/SwiftCrossUI/Modifiers/Frame.swift | 8 +- .../ViewGraph/ViewGraphNodeChildren.swift | 4 +- .../ViewGraph/ViewGraphNodeChildren.swift.gyb | 4 +- Sources/SwiftCrossUI/Views/Button.swift | 5 +- Sources/SwiftCrossUI/Views/EitherView.swift | 6 +- Sources/SwiftCrossUI/Views/ForEach.swift | 10 +- Sources/SwiftCrossUI/Views/HStack.swift | 2 +- Sources/SwiftCrossUI/Views/Image.swift | 2 +- Sources/SwiftCrossUI/Views/OptionalView.swift | 8 +- Sources/SwiftCrossUI/Views/Picker.swift | 17 +- Sources/SwiftCrossUI/Views/ScrollView.swift | 2 +- Sources/SwiftCrossUI/Views/Slider.swift | 19 +- Sources/SwiftCrossUI/Views/Spacer.swift | 43 +- Sources/SwiftCrossUI/Views/Table.swift | 14 +- Sources/SwiftCrossUI/Views/Text.swift | 5 +- Sources/SwiftCrossUI/Views/TextField.swift | 15 +- Sources/SwiftCrossUI/Views/ToggleButton.swift | 13 +- Sources/SwiftCrossUI/Views/ToggleSwitch.swift | 11 +- Sources/SwiftCrossUI/Views/VStack.swift | 2 +- Sources/SwiftCrossUI/Views/VariadicView.swift | 20 +- .../SwiftCrossUI/Views/VariadicView.swift.gyb | 2 +- Sources/SwiftCrossUI/Views/View.swift | 3 +- 29 files changed, 614 insertions(+), 841 deletions(-) diff --git a/Sources/AppKitBackend/AppKitBackend.swift b/Sources/AppKitBackend/AppKitBackend.swift index a6596394..eb1341cc 100644 --- a/Sources/AppKitBackend/AppKitBackend.swift +++ b/Sources/AppKitBackend/AppKitBackend.swift @@ -58,24 +58,18 @@ public struct AppKitBackend: AppBackend { public func show(widget: Widget) {} - public func createTextView(content: String, shouldWrap: Bool) -> Widget { - if shouldWrap { - return NSTextField(wrappingLabelWithString: content) - } else { - return NSTextField(labelWithString: content) - } + public func createTextView() -> Widget { + return NSTextField(wrappingLabelWithString: "") } - public func setContent(ofTextView textView: Widget, to content: String) { + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { + // TODO: Implement text wrap handling (textView as! NSTextField).stringValue = content } - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) {} - - public func createVStack(spacing: Int) -> Widget { + public func createVStack() -> Widget { let view = NSStackView() view.orientation = .vertical - view.spacing = CGFloat(spacing) return view } @@ -88,10 +82,9 @@ public struct AppKitBackend: AppBackend { (widget as! NSStackView).spacing = CGFloat(spacing) } - public func createHStack(spacing: Int) -> Widget { + public func createHStack() -> Widget { let view = NSStackView() view.orientation = .horizontal - view.spacing = CGFloat(spacing) return view } @@ -103,19 +96,12 @@ public struct AppKitBackend: AppBackend { (widget as! NSStackView).spacing = CGFloat(spacing) } - public func createButton(label: String, action: @escaping () -> Void) -> Widget { - let button = NSButton(title: label, target: nil, action: nil) - button.onAction = { _ in - action() - } - return button + public func createButton() -> Widget { + return NSButton(title: "", target: nil, action: nil) } - public func setLabel(ofButton button: Widget, to label: String) { + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { (button as! NSButton).title = label - } - - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { (button as! NSButton).onAction = { _ in action() } diff --git a/Sources/CursesBackend/CursesBackend.swift b/Sources/CursesBackend/CursesBackend.swift index 46105699..16dd068f 100644 --- a/Sources/CursesBackend/CursesBackend.swift +++ b/Sources/CursesBackend/CursesBackend.swift @@ -52,7 +52,7 @@ public final class CursesBackend: AppBackend { widget.setNeedsDisplay() } - public func createVStack(spacing: Int) -> Widget { + public func createVStack() -> Widget { return View() } @@ -64,7 +64,7 @@ public final class CursesBackend: AppBackend { public func setSpacing(ofVStack container: Widget, to spacing: Int) {} - public func createHStack(spacing: Int) -> Widget { + public func createHStack() -> Widget { return View() } @@ -76,30 +76,26 @@ public final class CursesBackend: AppBackend { public func setSpacing(ofHStack container: Widget, to spacing: Int) {} - public func createTextView(content: String, shouldWrap: Bool) -> Widget { - let label = Label(content) + public func createTextView() -> Widget { + let label = Label("") label.width = Dim.fill() return label } - public func setContent(ofTextView textView: Widget, to content: String) { + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { + // TODO: Implement text wrap handling let label = textView as! Label label.text = content } - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) {} - - public func createButton(label: String, action: @escaping () -> Void) -> Widget { - let button = TermKit.Button(label, clicked: action) + public func createButton() -> Widget { + let button = TermKit.Button("") button.height = Dim.sized(1) return button } - public func setLabel(ofButton button: Widget, to label: String) { + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { (button as! TermKit.Button).text = label - } - - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { (button as! TermKit.Button).clicked = { _ in action() } diff --git a/Sources/GtkBackend/GtkBackend.swift b/Sources/GtkBackend/GtkBackend.swift index 49925977..210af3bd 100644 --- a/Sources/GtkBackend/GtkBackend.swift +++ b/Sources/GtkBackend/GtkBackend.swift @@ -99,8 +99,10 @@ public final class GtkBackend: AppBackend { widget.show() } - public func createVStack(spacing: Int) -> Widget { - return Box(orientation: .vertical, spacing: spacing) + // MARK: Containers + + public func createVStack() -> Widget { + return Box(orientation: .vertical, spacing: 0) } public func addChild(_ child: Widget, toVStack container: Widget) { @@ -111,8 +113,8 @@ public final class GtkBackend: AppBackend { (container as! Box).spacing = spacing } - public func createHStack(spacing: Int) -> Widget { - return Box(orientation: .horizontal, spacing: spacing) + public func createHStack() -> Widget { + return Box(orientation: .horizontal, spacing: 0) } public func addChild(_ child: Widget, toHStack container: Widget) { @@ -123,50 +125,28 @@ public final class GtkBackend: AppBackend { (container as! Box).spacing = spacing } - public func createPassthroughVStack(spacing: Int) -> Widget { - return SectionBox(orientation: .vertical, spacing: spacing) + public func createSingleChildContainer() -> Widget { + return ModifierBox() } - public func addChild(_ child: Widget, toPassthroughVStack container: Widget) { - (container as! SectionBox).add(child) + public func setChild(ofSingleChildContainer container: Widget, to widget: Widget?) { + (container as! ModifierBox).setChild(widget) } - public func updatePassthroughVStack(_ vStack: Widget) { - (vStack as! SectionBox).update() + public func createLayoutTransparentStack() -> Widget { + return SectionBox(orientation: .vertical, spacing: 0) } - public func createEitherContainer(initiallyContaining child: Widget?) -> Widget { - let box = ModifierBox() - box.setChild(child) - return box + public func addChild(_ child: Widget, toLayoutTransparentStack container: Widget) { + (container as! SectionBox).add(child) } - public func setChild(ofEitherContainer container: Widget, to widget: Widget?) { - (container as! ModifierBox).setChild(widget) + public func removeChild(_ child: Widget, fromLayoutTransparentStack container: Widget) { + (container as! SectionBox).remove(child) } - public func createPaddingContainer(for child: Widget) -> Widget { - let box = ModifierBox() - box.setChild(child) - return box - } - - public func getChild(ofPaddingContainer container: Widget) -> Widget { - return (container as! ModifierBox).child! - } - - public func setPadding( - ofPaddingContainer container: Widget, - top: Int, - bottom: Int, - leading: Int, - trailing: Int - ) { - let container = container as! ModifierBox - container.marginTop = top - container.marginBottom = bottom - container.marginStart = leading - container.marginEnd = trailing + public func updateLayoutTransparentStack(_ container: Widget) { + (container as! SectionBox).update() } public func createScrollContainer(for child: Widget) -> Widget { @@ -176,75 +156,79 @@ public final class GtkBackend: AppBackend { return scrolledWindow } - public func createButton(label: String, action: @escaping () -> Void) -> Widget { - let button = Button() - button.label = label - button.clicked = { _ in action() } - return button + public func createOneOfContainer() -> Widget { + return Stack(transitionDuration: 300, transitionType: .slideLeftRight) } - public func setLabel(ofButton button: Widget, to label: String) { - (button as! Gtk.Button).label = label + public func addChild(_ child: Widget, toOneOfContainer container: Widget) { + (container as! Stack).add(child, named: UUID().uuidString) } - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { - (button as! Gtk.Button).clicked = { _ in action() } + public func removeChild(_ child: Widget, fromOneOfContainer container: Widget) { + (container as! Stack).remove(child) } - public func createToggle( - label: String, - active: Bool, - onChange: @escaping (Bool) -> Void - ) -> Widget { - let toggle = ToggleButton() - toggle.label = label - toggle.active = active - return toggle + public func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) { + (container as! Stack).setVisible(child) } - public func setIsActive(ofToggle toggle: Widget, to active: Bool) { - (toggle as! Gtk.ToggleButton).active = active + public func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget { + let widget = Paned(orientation: .horizontal) + widget.startChild = leadingChild + widget.endChild = trailingChild + widget.shrinkStartChild = false + widget.shrinkEndChild = false + // Set the position to the farthest left possible. + // TODO: Allow setting the default offset (SwiftUI api: `navigationSplitViewColumnWidth(min:ideal:max:)`). + // This needs frame modifier to be fleshed out first + widget.position = 0 + widget.expandVertically = true + return widget } - public func setOnChange(ofToggle toggle: Widget, to onChange: @escaping (Bool) -> Void) { - (toggle as! Gtk.ToggleButton).toggled = { widget in - onChange(widget.active) - } - } + // MARK: Layout - public func createSwitch(active: Bool, onChange: @escaping (Bool) -> Void) -> Widget { - let switchWidget = Switch() - switchWidget.active = active - switchWidget.notifyActive = { widget in - onChange(widget.active) - } - return switchWidget + public func createSpacer() -> Widget { + return ModifierBox() } - public func setIsActive(ofSwitch switchWidget: Widget, to active: Bool) { - (switchWidget as! Gtk.Switch).active = active + public func updateSpacer( + _ spacer: Widget, + expandHorizontally: Bool, + expandVertically: Bool, + minSize: Int + ) { + let spacer = spacer as! ModifierBox + spacer.expandHorizontally = expandHorizontally + spacer.expandVertically = expandVertically + spacer.marginEnd = expandHorizontally ? minSize : 0 + spacer.marginBottom = expandVertically ? minSize : 0 } - public func setOnChange(ofSwitch switchWidget: Widget, to onChange: @escaping (Bool) -> Void) { - (switchWidget as! Gtk.Switch).notifyActive = { widget in - onChange(widget.active) + public func getInheritedOrientation(of widget: Widget) -> InheritedOrientation? { + let parent = widget.firstNonModifierParent() as? Box + switch parent?.orientation { + case .vertical: + return .vertical + case .horizontal: + return .horizontal + case nil: + return nil } } - public func createTextView(content: String, shouldWrap: Bool) -> Widget { - let label = Label(string: content) - label.lineWrapMode = .wordCharacter + // MARK: Passive views + + public func createTextView() -> Widget { + let label = Label(string: "") label.horizontalAlignment = .start - label.wrap = shouldWrap return label } - public func setContent(ofTextView textView: Widget, to content: String) { - (textView as! Label).label = content - } - - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) { - (textView as! Label).wrap = shouldWrap + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { + let textView = textView as! Label + textView.label = content + textView.wrap = shouldWrap } public func createImageView(filePath: String) -> Widget { @@ -255,176 +239,176 @@ public final class GtkBackend: AppBackend { return picture } - public func setFilePath(ofImageView imageView: Widget, to filePath: String) { + public func updateImageView(_ imageView: Widget, filePath: String) { (imageView as! Gtk.Picture).setPath(filePath) } - public func createSpacer( - expandHorizontally: Bool, expandVertically: Bool - ) -> Widget { - let box = ModifierBox() - box.expandHorizontally = expandHorizontally - box.expandVertically = expandVertically - return box + private class Tables { + var tableSizes: [ObjectIdentifier: (rows: Int, columns: Int)] = [:] } - public func setExpandHorizontally(ofSpacer spacer: Widget, to expandHorizontally: Bool) { - (spacer as! ModifierBox).expandHorizontally = expandHorizontally - } + private let tables = Tables() - public func setExpandVertically(ofSpacer spacer: Widget, to expandVertically: Bool) { - (spacer as! ModifierBox).expandVertically = expandVertically - } + public func createTable(rows: Int, columns: Int) -> Widget { + let widget = Grid() - public func getInheritedOrientation(of widget: Widget) -> InheritedOrientation? { - let parent = widget.firstNonModifierParent() as? Box - switch parent?.orientation { - case .vertical: - return .vertical - case .horizontal: - return .horizontal - case nil: - return nil + for i in 0.. Void - ) -> Widget { - let scale = Scale() - scale.expandHorizontally = true - scale.minimum = minimum - scale.maximum = maximum - scale.value = value - scale.digits = decimalPlaces - scale.valueChanged = { widget in - onChange(widget.value) + for i in 0.. Void) { - (slider as! Scale).valueChanged = { widget in - onChange(widget.value) + let rowDifference = rows - tables.tableSizes[ObjectIdentifier(table)]!.rows + tables.tableSizes[ObjectIdentifier(table)]!.rows = rows + if rowDifference < 0 { + for _ in 0..<(-rowDifference) { + table.removeRow(position: 0) + } + } else if rowDifference > 0 { + for _ in 0.. Void - ) -> Widget { - let textField = Entry() - textField.text = content - textField.placeholderText = placeholder - textField.changed = { widget in - onChange(widget.text) + public func setColumnCount(ofTable table: Widget, to columns: Int) { + let table = table as! Grid + + let columnDifference = columns - tables.tableSizes[ObjectIdentifier(table)]!.columns + tables.tableSizes[ObjectIdentifier(table)]!.columns = columns + if columnDifference < 0 { + for _ in 0..<(-columnDifference) { + table.removeColumn(position: 0) + } + } else if columnDifference > 0 { + for _ in 0.. Widget { + return Button() } - public func setOnChange( - ofTextField textField: Widget, to onChange: @escaping (String) -> Void - ) { - (textField as! Entry).changed = { widget in - onChange(widget.text) + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { + let button = button as! Gtk.Button + button.label = label + button.clicked = { _ in action() } + } + + public func createToggle() -> Widget { + return ToggleButton() + } + + public func updateToggle(_ toggle: Widget, label: String, onChange: @escaping (Bool) -> Void) { + (toggle as! ToggleButton).label = label + (toggle as! Gtk.ToggleButton).toggled = { widget in + onChange(widget.active) } } - public func getContent(ofTextField textField: Widget) -> String { - return (textField as! Entry).text + public func setState(ofToggle toggle: Widget, to state: Bool) { + (toggle as! Gtk.ToggleButton).active = state } - public func createListView() -> Widget { - return SectionBox(orientation: .vertical, spacing: 0) + public func createSwitch() -> Widget { + return Switch() } - public func addChild(_ child: Widget, toListView listView: Widget) { - (listView as! SectionBox).add(child) + public func updateSwitch(_ switchWidget: Widget, onChange: @escaping (Bool) -> Void) { + (switchWidget as! Gtk.Switch).notifyActive = { widget in + onChange(widget.active) + } } - public func removeChild(_ child: Widget, fromListView listView: Widget) { - (listView as! SectionBox).remove(child) + public func setState(ofSwitch switchWidget: Widget, to state: Bool) { + (switchWidget as! Gtk.Switch).active = state } - public func updateListView(_ listView: Widget) { - (listView as! SectionBox).update() + public func createSlider() -> Widget { + let scale = Scale() + scale.expandHorizontally = true + return scale } - public func createOneOfContainer() -> Widget { - return Stack(transitionDuration: 300, transitionType: .slideLeftRight) + public func updateSlider( + _ slider: Widget, + minimum: Double, + maximum: Double, + decimalPlaces: Int, + onChange: @escaping (Double) -> Void + ) { + let slider = slider as! Scale + slider.minimum = minimum + slider.maximum = maximum + slider.digits = decimalPlaces + slider.valueChanged = { widget in + onChange(widget.value) + } } - public func addChild(_ child: Widget, toOneOfContainer container: Widget) { - (container as! Stack).add(child, named: UUID().uuidString) + public func setValue(ofSlider slider: Widget, to value: Double) { + (slider as! Scale).value = value } - public func removeChild(_ child: Widget, fromOneOfContainer container: Widget) { - (container as! Stack).remove(child) + public func createTextField() -> Widget { + return Entry() } - public func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) { - (container as! Stack).setVisible(child) + public func updateTextField( + _ textField: Widget, placeholder: String, onChange: @escaping (String) -> Void + ) { + let textField = textField as! Entry + textField.placeholderText = placeholder + textField.changed = { widget in + onChange(widget.text) + } } - public func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget { - let widget = Paned(orientation: .horizontal) - widget.startChild = leadingChild - widget.endChild = trailingChild - widget.shrinkStartChild = false - widget.shrinkEndChild = false - // Set the position to the farthest left possible. - // TODO: Allow setting the default offset (SwiftUI api: `navigationSplitViewColumnWidth(min:ideal:max:)`). - // This needs frame modifier to be fledged out first - widget.position = 0 - widget.expandVertically = true - return widget + public func setContent(ofTextField textField: Widget, to content: String) { + (textField as! Entry).text = content } - public func createPicker( - options: [String], selectedOption: Int?, onChange: @escaping (Int?) -> Void - ) -> Widget { - let optionStrings = options.map({ "\($0)" }) - let widget = DropDown(strings: optionStrings) + public func getContent(ofTextField textField: Widget) -> String { + return (textField as! Entry).text + } - let options = options - widget.notifySelected = { widget in - if widget.selected >= options.count { - onChange(nil) - } else { - onChange(widget.selected) - } - } - return widget + public func createPicker() -> Widget { + return DropDown(strings: []) } - public func setOptions(ofPicker picker: Widget, to options: [String]) { + public func updatePicker( + _ picker: Widget, options: [String], onChange: @escaping (Int?) -> Void + ) { let picker = picker as! DropDown // Check whether the options need to be updated or not (avoiding unnecessary updates is @@ -457,6 +441,14 @@ public final class GtkBackend: AppBackend { .baseAddress ) ) + + picker.notifySelected = { picker in + if picker.selected == GTK_INVALID_LIST_POSITION { + onChange(nil) + } else { + onChange(picker.selected) + } + } } public func setSelectedOption(ofPicker picker: Widget, to selectedOption: Int?) { @@ -466,113 +458,52 @@ public final class GtkBackend: AppBackend { } } - public func setOnChange(ofPicker picker: Widget, to onChange: @escaping (Int?) -> Void) { - (picker as! DropDown).notifySelected = { picker in - if picker.selected == GTK_INVALID_LIST_POSITION { - onChange(nil) - } else { - onChange(picker.selected) - } - } - } + // MARK: Modifiers - public func createFrameContainer(for child: Widget, minWidth: Int, minHeight: Int) -> Widget { - setMinWidth(ofFrameContainer: child, to: minWidth) - setMinHeight(ofFrameContainer: child, to: minHeight) - return child + public func createFrameContainer(for child: Widget) -> Widget { + let widget = ModifierBox() + widget.setChild(child) + return widget } - public func setMinWidth(ofFrameContainer container: Widget, to minWidth: Int) { + public func updateFrameContainer(_ container: Widget, minWidth: Int, minHeight: Int) { + let container = container as! ModifierBox container.css.set(properties: [.minWidth(minWidth)], clear: false) + container.css.set(properties: [.minHeight(minHeight)], clear: false) } - public func setMinHeight(ofFrameContainer container: Widget, to minHeight: Int) { - container.css.set(properties: [.minHeight(minHeight)], clear: false) + public func createPaddingContainer(for child: Widget) -> Widget { + let box = ModifierBox() + box.setChild(child) + return box } - public func createForegroundColorContainer(for child: Widget, color: SwiftCrossUI.Color) + public func setPadding( + ofPaddingContainer container: Widget, + top: Int, + bottom: Int, + leading: Int, + trailing: Int + ) { + let container = container as! ModifierBox + container.marginTop = top + container.marginBottom = bottom + container.marginStart = leading + container.marginEnd = trailing + } + + public func createStyleContainer(for child: Widget) -> Widget { let widget = ModifierBox() widget.setChild(child) - setForegroundColor(ofForegroundColorContainer: widget, to: color) return widget } public func setForegroundColor( - ofForegroundColorContainer container: Widget, + ofStyleContainer container: Widget, to color: SwiftCrossUI.Color ) { (container as! ModifierBox).css.set(properties: [.foregroundColor(color.gtkColor)]) } - - private class Tables { - var tableSizes: [ObjectIdentifier: (rows: Int, columns: Int)] = [:] - } - - private let tables = Tables() - - public func createTable(rows: Int, columns: Int) -> Widget { - let widget = Grid() - - for i in 0.. 0 { - for _ in 0.. 0 { - for _ in 0.. Widget { + public func createVStack() -> Widget { let grid = Grid { parent in - let grid = LVGrid(with: parent, rows: 0, columns: 1, padding: Int16(spacing)) + let grid = LVGrid(with: parent, rows: 0, columns: 1, padding: 0) grid.size = LVSize(width: 1 << 13 | 2001, height: 1 << 13 | 2001) grid.center() return grid @@ -116,9 +116,9 @@ public final class LVGLBackend: AppBackend { } } - public func createHStack(spacing: Int) -> Widget { + public func createHStack() -> Widget { let grid = Grid { parent in - let grid = LVGrid(with: parent, rows: 1, columns: 0, padding: Int16(spacing)) + let grid = LVGrid(with: parent, rows: 1, columns: 0, padding: 0) grid.size = LVSize(width: 1 << 13 | 2001, height: 1 << 13 | 2001) grid.center() return grid @@ -146,47 +146,35 @@ public final class LVGLBackend: AppBackend { } } - public func createTextView(content: String, shouldWrap: Bool) -> Widget { + public func createTextView() -> Widget { return Widget { parent in let label = LVLabel(with: parent) - label.text = content return label } } - public func setContent(ofTextView textView: Widget, to content: String) { + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { + // TODO: Implement text wrap option textView.postCreationAction { widget in (widget as! LVLabel).text = content } } - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) { - // TODO: Implement text wrap option - } - - public func createButton(label: String, action: @escaping () -> Void) -> Widget { + public func createButton() -> Widget { return Widget { parent in let button = LVButton(with: parent) let buttonLabel = LVLabel(with: button) - buttonLabel.text = label buttonLabel.center() - button.onEvent = { event in - if event.code == LV_EVENT_PRESSED { - action() - } - } return button } } - public func setLabel(ofButton button: Widget, to label: String) { + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { button.postCreationAction { widget in let widget = widget as! LVButton (widget.child(at: 0)! as! LVLabel).text = label } - } - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { button.postCreationAction { widget in let widget = widget as! LVButton widget.onEvent = { event in diff --git a/Sources/QtBackend/QtBackend.swift b/Sources/QtBackend/QtBackend.swift index 63b3fd52..45d8d082 100644 --- a/Sources/QtBackend/QtBackend.swift +++ b/Sources/QtBackend/QtBackend.swift @@ -72,10 +72,8 @@ public struct QtBackend: AppBackend { widget.show() } - public func createVStack(spacing: Int) -> Widget { + public func createVStack() -> Widget { let layout = QVBoxLayout() - layout.spacing = Int32(spacing) - let widget = QWidget() widget.layout = layout return widget @@ -89,10 +87,8 @@ public struct QtBackend: AppBackend { (widget.layout as! QVBoxLayout).spacing = Int32(spacing) } - public func createHStack(spacing: Int) -> Widget { + public func createHStack() -> Widget { let layout = QHBoxLayout() - layout.spacing = Int32(spacing) - let widget = QWidget() widget.layout = layout return widget @@ -106,51 +102,20 @@ public struct QtBackend: AppBackend { (widget.layout as! QHBoxLayout).spacing = Int32(spacing) } - public func createPaddingContainer(for child: Widget) -> Widget { - let container = createVStack(spacing: 0) - addChild(child, toVStack: container) - internalState.paddingContainerChildren[ObjectIdentifier(container)] = child - return container - } - - public func getChild(ofPaddingContainer container: Widget) -> Widget { - return internalState.paddingContainerChildren[ObjectIdentifier(container)]! + public func createTextView() -> Widget { + return QLabel(text: "") } - public func setPadding( - ofPaddingContainer container: Widget, - top: Int, - bottom: Int, - leading: Int, - trailing: Int - ) { - (container.layout! as! QVBoxLayout).contentsMargins = QMargins( - left: Int32(leading), - top: Int32(top), - right: Int32(trailing), - bottom: Int32(bottom) - ) - } - - public func createTextView(content: String, shouldWrap: Bool) -> Widget { - let label = QLabel(text: content) - return label - } - - public func setContent(ofTextView textView: Widget, to content: String) { - (textView as! QLabel).text = content - } - - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) { + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { // TODO: Implement text wrap setting + (textView as! QLabel).text = content } - public func createButton(label: String, action: @escaping () -> Void) -> Widget { - let button = QPushButton(text: label) + public func createButton() -> Widget { + let button = QPushButton(text: "") // Internal state is required to avoid multiple subsequent calls to setAction adding // new handlers instead of replacing the existing handler - internalState.buttonClickActions[ObjectIdentifier(button)] = action button.connectClicked(receiver: nil) { [weak internalState] in guard let internalState = internalState else { return @@ -161,27 +126,13 @@ public struct QtBackend: AppBackend { return button } - public func setLabel(ofButton button: Widget, to label: String) { + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { (button as! QPushButton).text = label - } - - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { internalState.buttonClickActions[ObjectIdentifier(button)] = action } - public func createSlider( - minimum: Double, - maximum: Double, - value: Double, - decimalPlaces: Int, - onChange: @escaping (Double) -> Void - ) -> QWidget { + public func createSlider() -> QWidget { let slider = QSlider(orientation: .Horizontal) - slider.minimum = Int32(minimum) - slider.maximum = Int32(maximum) - slider.value = Int32(value) - - internalState.sliderChangeActions[ObjectIdentifier(slider)] = onChange slider.connectValueChanged(receiver: nil) { [weak internalState] val in guard let internalState = internalState else { return @@ -192,24 +143,40 @@ public struct QtBackend: AppBackend { return slider } - public func setMinimum(ofSlider slider: Widget, to minimum: Double) { - (slider as! QSlider).minimum = Int32(minimum) - } - - public func setMaximum(ofSlider slider: Widget, to maximum: Double) { - (slider as! QSlider).maximum = Int32(maximum) + public func updateSlider( + _ slider: Widget, minimum: Double, maximum: Double, decimalPlaces: Int, + onChange: @escaping (Double) -> Void + ) { + let slider = slider as! QSlider + slider.minimum = Int32(minimum) + slider.maximum = Int32(maximum) + internalState.sliderChangeActions[ObjectIdentifier(slider)] = onChange } public func setValue(ofSlider slider: Widget, to value: Double) { (slider as! QSlider).value = Int32(value) } - /// non applicable here - public func setDecimalPlaces(ofSlider slider: Widget, to decimalPlaces: Int) { + public func createPaddingContainer(for child: Widget) -> Widget { + let container = createVStack() + addChild(child, toVStack: container) + internalState.paddingContainerChildren[ObjectIdentifier(container)] = child + return container } - public func setOnChange(ofSlider slider: Widget, to onChange: @escaping (Double) -> Void) { - internalState.sliderChangeActions[ObjectIdentifier(slider)] = onChange + public func setPadding( + ofPaddingContainer container: Widget, + top: Int, + bottom: Int, + leading: Int, + trailing: Int + ) { + (container.layout! as! QVBoxLayout).contentsMargins = QMargins( + left: Int32(leading), + top: Int32(top), + right: Int32(trailing), + bottom: Int32(bottom) + ) } } diff --git a/Sources/SwiftCrossUI/Backend/AppBackend.swift b/Sources/SwiftCrossUI/Backend/AppBackend.swift index 0ba8da46..41e66655 100644 --- a/Sources/SwiftCrossUI/Backend/AppBackend.swift +++ b/Sources/SwiftCrossUI/Backend/AppBackend.swift @@ -21,8 +21,27 @@ var currentBackend: (any AppBackend)! /// /// If you need to modify the children of a widget after creation but there /// aren't update methods available, this is an intentional limitation to -/// reduce the complexity of maintaining a multitude of backends. Use -/// a nested container such as a VStack to work around this limitation. +/// reduce the complexity of maintaining a multitude of backends -- nest +/// another container, such as a VStack, inside the container to allow you +/// to change its children on demand. +/// +/// For interactive controls with values, the method for setting the +/// control's value is always separate from the method for updating the +/// control's properties (e.g. its minimum value, or placeholder label etc). +/// This is because it's very common for view implementations to either +/// update a control's properties without updating its value (in the case +/// of an unbound control), or update a control's value only if it doesn't +/// match its current value (to prevent infinite loops). +/// +/// Many views have both a `create` and an `update` method. The `create` +/// method should only have parameters for properties which don't have +/// sensible defaults (e.g. under some backends, image widgets can't be +/// created without an underlying image being selected up-front, so the +/// `create` method requires a `filePath` and will overlap with the `update` +/// method). This design choice was made to reduce the amount of repeated +/// code between the `create` and `update` methods of the various widgets +/// (since the `update` method is always called between calling `create` +/// and actually displaying the widget anyway). public protocol AppBackend { associatedtype Window associatedtype Widget @@ -87,213 +106,205 @@ public protocol AppBackend { /// propagating updates. func show(widget: Widget) - /// Creates a vertical container with the specified spacing between children. - /// Predominantly used by ``VStack``.` - func createVStack(spacing: Int) -> Widget + // MARK: Containers + + /// Creates a vertical container. Predominantly used by ``VStack``.` + func createVStack() -> Widget /// Adds a child to the end of a vertical container. func addChild(_ child: Widget, toVStack container: Widget) /// Sets the spacing between children of a vertical container. func setSpacing(ofVStack widget: Widget, to spacing: Int) - /// Creates a horizontal container with the specified spacing between children. - /// Predominantly used by ``HStack``.` - func createHStack(spacing: Int) -> Widget + /// Creates a horizontal container. Predominantly used by ``HStack``.` + func createHStack() -> Widget /// Adds a child to the end of a horizontal container. func addChild(_ child: Widget, toHStack container: Widget) /// Sets the spacing between children of a horizontal container. func setSpacing(ofHStack widget: Widget, to spacing: Int) - /// Creates a container which takes on the orientation of its parent (preferring to be - /// vertical if it doesn't have any oriented parents). Sometimes referred to as - /// layout-transparent in SwiftCrossUI code. Predominantly used by the variadic views - /// and ``ViewGraphNodeChildren`` implementations (to avoid partaking in layout). - func createPassthroughVStack(spacing: Int) -> Widget - /// Adds a child to a layout-transparent container. - func addChild(_ child: Widget, toPassthroughVStack container: Widget) - /// Updates the orientation of a layout-transparent container to match its nearest - /// oriented parent (preferring to be vertical if it doesn't have any oriented - /// parents). Called after any change that could potentially affect parent orientations. - func updatePassthroughVStack(_ vStack: Widget) - - /// Creates a single-child container suitable for ``EitherView``. - func createEitherContainer(initiallyContaining child: Widget?) -> Widget + /// Creates a single-child container. Predominantly used to implement ``EitherView``. + func createSingleChildContainer() -> Widget /// Sets the single child of an either container. - func setChild(ofEitherContainer container: Widget, to widget: Widget?) - - /// Creates a single-child container with configurable padding. Predominantly used - /// to implement the ``View/padding(_:)`` and ``View/padding(_:_:)`` modifiers. - func createPaddingContainer(for child: Widget) -> Widget - /// Gets the single child of a padding container. - func getChild(ofPaddingContainer container: Widget) -> Widget - /// Sets the padding of a padding container. - func setPadding( - ofPaddingContainer container: Widget, - top: Int, - bottom: Int, - leading: Int, - trailing: Int - ) + func setChild(ofSingleChildContainer container: Widget, to widget: Widget?) + + /// Creates a view with a (theoretically) unlimited number of children, which inherits the + /// orientation of its nearest oriented parent. Should be vertical if it doesn't have any + /// oriented parents. Often used as a layout-transparent container (e.g. by + /// ``ViewGraphNodeChildren`` implementations which use it to avoid partaking in layout). + /// Also used by to implement ``ForEach``. + func createLayoutTransparentStack() -> Widget + /// Adds a child to the end of a layout-transparent stack. + func addChild(_ child: Widget, toLayoutTransparentStack container: Widget) + /// Removes a child from a layout-transparent stack. Does nothing if the child doesn't exist. + func removeChild(_ child: Widget, fromLayoutTransparentStack container: Widget) + /// Updates a layout-transparent stack's orientation to match that of its nearest oriented + /// parent. + func updateLayoutTransparentStack(_ container: Widget) /// Creates a scrollable single-child container wrapping the given widget. func createScrollContainer(for child: Widget) -> Widget - /// Creates a labelled button with an action triggered on click. Predominantly used - /// by ``Button``. - func createButton(label: String, action: @escaping () -> Void) -> Widget - /// Sets a button's label. - func setLabel(ofButton button: Widget, to label: String) - /// Sets a button's action (triggered on click). Replaces any existing actions. - func setAction(ofButton button: Widget, to action: @escaping () -> Void) + /// Creates a container that can (theoretically) have an unlimited number of children + /// while only displaying one child at a time (selected using ``Backend/setVisibleChild``). + /// Differs from ``AppBackend/createSingleChildContainer()`` because it allows + /// transitions to be displayed when switching between children (unlike the single child + /// container which only ever has the current widget as its child meaning that it can't + /// do transitions). + func createOneOfContainer() -> Widget + /// Adds a child to a one-of container. + func addChild(_ child: Widget, toOneOfContainer container: Widget) + /// Removes a child from a one-of container. + func removeChild(_ child: Widget, fromOneOfContainer container: Widget) + /// Sets the visible child of a one-of container. Uses a widget reference instead of an + /// index since the visible child should remain the same even if the visible child's + /// index changes (e.g. due to a child being removed from before the visible child). + func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) - /// Creates a labelled toggle that is either on or off. Predominantly used by - /// ``Toggle``. - func createToggle( - label: String, active: Bool, onChange: @escaping (Bool) -> Void - ) -> Widget - /// Sets the state of the button to active or not. - func setIsActive(ofToggle toggle: Widget, to active: Bool) - /// Sets the change handler of a toggle (replaces any existing change handlers). - /// The change handler is called whenever the button is toggled on or off. - func setOnChange(ofToggle toggle: Widget, to onChange: @escaping (Bool) -> Void) + /// Creates a split view containing two children visible side by side. + /// + /// If you need to modify the leading and trailing children after creation nest them + /// inside another container such as a VStack (avoiding update methods makes maintaining + /// a multitude of backends a bit easier). + func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget - /// Creates a switch that is either on or off. Predominantly used by ``Switch`` - func createSwitch(active: Bool, onChange: @escaping (Bool) -> Void) -> Widget - /// Sets the state of the switch to active or not. - func setIsActive(ofSwitch switchWidget: Widget, to active: Bool) - /// Sets the change handler of a switch (replaces any existing change handlers). - /// The change handler is called whenever the switch is turned on or off. - func setOnChange(ofSwitch switchWidget: Widget, to onChange: @escaping (Bool) -> Void) + // MARK: Layout + + /// Creates a contentless spacer that can expand along either axis (or both). The spacer + /// can have a minimum size to ensure that it takes up at least a cetain amount of space. + func createSpacer() -> Widget + /// Sets whether a spacer should expand along the horizontal and vertical axes, along + /// with a minimum size to use along expanding axes. + func updateSpacer( + _ spacer: Widget, + expandHorizontally: Bool, + expandVertically: Bool, + minSize: Int + ) + + /// Gets the orientation of a widget's first oriented parent (if any). + func getInheritedOrientation(of widget: Widget) -> InheritedOrientation? + + // MARK: Passive views /// Creates a non-editable text view with optional text wrapping. Predominantly used /// by ``Text``.` - func createTextView(content: String, shouldWrap: Bool) -> Widget - /// Sets the content of a non-editable text view. - func setContent(ofTextView textView: Widget, to content: String) - /// Sets whether to wrap the text content of a non-editable text view or not. - func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) + func createTextView() -> Widget + /// Sets the content and wrapping mode of a non-editable text view. If `shouldWrap` + /// is `false`, text shouldn't be wrapped onto multiple lines. + func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) /// Creates an image view from an image file (specified by path). Predominantly used /// by ``Image``. func createImageView(filePath: String) -> Widget /// Sets the path of the image file being displayed by an image view. - func setFilePath(ofImageView imageView: Widget, to filePath: String) + func updateImageView(_ imageView: Widget, filePath: String) + + /// Creates a table with an initial number of rows and columns. + func createTable(rows: Int, columns: Int) -> Widget + /// Sets the number of rows of a table. Existing rows outside of the new bounds should + /// be deleted. + func setRowCount(ofTable table: Widget, to rows: Int) + /// Sets the number of columns of a table. Existing columns outside of the new bounds + /// should be deleted. + func setColumnCount(ofTable table: Widget, to columns: Int) + /// Sets the contents of the table cell at the given position in a table. + func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) - /// Creates contentless spacer that can expand along either axis or both. - func createSpacer( - expandHorizontally: Bool, expandVertically: Bool - ) -> Widget - /// Sets whether a spacer should expand along the horizontal axis. - func setExpandHorizontally(ofSpacer spacer: Widget, to expandHorizontally: Bool) - /// Sets whether a spacer should expand along the vertical axis. - func setExpandVertically(ofSpacer spacer: Widget, to expandVertically: Bool) + // MARK: Controls - /// Gets the orientation of a widget's first oriented parent (if any). - func getInheritedOrientation(of widget: Widget) -> InheritedOrientation? + /// Creates a labelled button with an action triggered on click. Predominantly used + /// by ``Button``. + func createButton() -> Widget + /// Sets a button's label and action. The action replaces any existing actions.. + func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) + + /// Creates a labelled toggle that is either on or off. Predominantly used by + /// ``Toggle``. + func createToggle() -> Widget + /// Sets the label and change handler of a toggle (replaces any existing change handlers). + /// The change handler is called whenever the button is toggled on or off. + func updateToggle(_ toggle: Widget, label: String, onChange: @escaping (Bool) -> Void) + /// Sets the state of the button to active or not. + func setState(ofToggle toggle: Widget, to state: Bool) + + /// Creates a switch that is either on or off. Predominantly used by ``Switch`` + func createSwitch() -> Widget + /// Sets the change handler of a switch (replaces any existing change handlers). + /// The change handler is called whenever the button is toggled on or off. + func updateSwitch(_ switchWidget: Widget, onChange: @escaping (Bool) -> Void) + /// Sets the state of the switch to active or not. + func setState(ofSwitch switchWidget: Widget, to state: Bool) - /// Creates a slider for choosing a numerical value from a range with a limit - /// on the number of decimal places displayed and a change handler. Predominantly used + /// Creates a slider for choosing a numerical value from a range. Predominantly used /// by ``Slider``. - func createSlider( + func createSlider() -> Widget + /// Sets the minimum and maximum selectable value of a slider (inclusive), the number of decimal + /// places displayed by the slider, and the slider's change handler (replaces any existing + /// change handlers). + func updateSlider( + _ slider: Widget, minimum: Double, maximum: Double, - value: Double, decimalPlaces: Int, onChange: @escaping (Double) -> Void - ) -> Widget - /// Sets the minimum selectable value of a slider (inclusive). - func setMinimum(ofSlider slider: Widget, to minimum: Double) - /// Sets the maximum selectable value of a slider (inclusive). - func setMaximum(ofSlider slider: Widget, to maximum: Double) + ) /// Sets the selected value of a slider. func setValue(ofSlider slider: Widget, to value: Double) - /// Sets the number of decimal places displayed by a slider. - func setDecimalPlaces(ofSlider slider: Widget, to decimalPlaces: Int) - /// Sets the change handler of a slider (replaces any existing change handlers). - func setOnChange(ofSlider slider: Widget, to onChange: @escaping (Double) -> Void) /// Creates an editable text field with a placeholder label and change handler. The /// change handler is called whenever the displayed value changes. Predominantly /// used by ``TextField``. - func createTextField( - content: String, placeholder: String, onChange: @escaping (String) -> Void - ) -> Widget - /// Sets the value of an editable text field. - func setContent(ofTextField textField: Widget, to content: String) - /// Sets the placeholder label of an editable text field. - func setPlaceholder(ofTextField textField: Widget, to placeholder: String) - /// Sets the change handler of an editable text field (replace any existing change - /// handlers). The change handler is called whenever the displayed value changes. + func createTextField() -> Widget + /// Sets the placeholder label and change handler of an editable text field. The new + /// change handler replaces any existing change handlers, and is called whenever the + /// displayed value changes. + /// /// The backend shouldn't wait until the user finishes typing to call the change handler; /// it should allow live access to the value. - func setOnChange(ofTextField textField: Widget, to onChange: @escaping (String) -> Void) + func updateTextField( + _ textField: Widget, placeholder: String, onChange: @escaping (String) -> Void + ) + /// Sets the value of an editable text field. + func setContent(ofTextField textField: Widget, to content: String) /// Gets the value of an editable text field. func getContent(ofTextField textField: Widget) -> String - /// Creates a view with a (theoretically) unlimited number of children. Should inherit the - /// orientation of its nearest oriented parent. - func createListView() -> Widget - /// Adds a child to the end of a list view. - func addChild(_ child: Widget, toListView listView: Widget) - /// Removes a child from wherever it is in a list view (if it exists). - func removeChild(_ child: Widget, fromListView listView: Widget) - /// Updates the list view's orientation to match that of its nearest oriented parent. - func updateListView(_ listView: Widget) - - /// Creates a container that can (theoretically) have an unlimited number of children - /// while only displaying one child at a time (selected using ``Backend/setVisibleChild``). - func createOneOfContainer() -> Widget - /// Adds a child to a one-of container. - func addChild(_ child: Widget, toOneOfContainer container: Widget) - /// Removes a child from a one-of container. - func removeChild(_ child: Widget, fromOneOfContainer container: Widget) - /// Sets the visible child of a one-of container. - func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) - - /// Creates a split view containing two children visible side by side. - /// - /// If you need to modify the leading and trailing children after creation nest them - /// inside another container such as a VStack (avoiding update methods makes maintaining - /// a multitude of backends a bit easier). - func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget - /// Creates a picker for selecting from a finite set of options (e.g. a radio button group, /// a drop-down, a picker wheel). Predominantly used by ``Picker``. The change handler is /// called whenever a selection is made (even if the same option is picked again). - func createPicker( - options: [String], selectedOption: Int?, onChange: @escaping (Int?) -> Void - ) -> Widget - /// Sets the options for a picker to display. - func setOptions(ofPicker picker: Widget, to options: [String]) + func createPicker() -> Widget + /// Sets the options for a picker to display, along with a change handler for when its + /// selected option changes. The change handler replaces any existing change handlers and + /// is called whenever a selection is made (even if the same option is picked again). + func updatePicker(_ picker: Widget, options: [String], onChange: @escaping (Int?) -> Void) /// Sets the index of the selected option of a picker. func setSelectedOption(ofPicker picker: Widget, to selectedOption: Int?) - /// Sets the change handler of a picker (replaces any existing change handlers). The change - /// handler is called whenever a selection is made (even if the same option is picked again). - func setOnChange(ofPicker picker: Widget, to onChange: @escaping (Int?) -> Void) - /// Creates a single-child container which can have size constraints. Predominantly used to + // MARK: Modifiers + + /// Creates a single-child container which can have size constraints. Used to /// implement the ``View/frame(minWidth:maxWidth:)`` modifier. - func createFrameContainer(for child: Widget, minWidth: Int, minHeight: Int) -> Widget - /// Sets the minimum width of a frame container. - func setMinWidth(ofFrameContainer container: Widget, to minWidth: Int) - /// Sets the minimum height of a frame container. - func setMinHeight(ofFrameContainer container: Widget, to minHeight: Int) - - /// Creates a single-child container which can control the foreground text color of its - /// child. Predominantly used to implement the ``View/foregroundColor(_:)`` modifier. - func createForegroundColorContainer(for child: Widget, color: Color) -> Widget - /// Sets the foreground color of a foreground color container. - func setForegroundColor(ofForegroundColorContainer container: Widget, to color: Color) + func createFrameContainer(for child: Widget) -> Widget + /// Sets the minimum width and minimum height of a frame container. + func updateFrameContainer(_ container: Widget, minWidth: Int, minHeight: Int) - /// Creates a table with an initial number of rows and columns. - func createTable(rows: Int, columns: Int) -> Widget - /// Sets the number of rows of a table. Existing rows outside of the new bounds should - /// be deleted. - func setRowCount(ofTable table: Widget, to rows: Int) - /// Sets the number of columns of a table. Existing columns outside of the new bounds - /// should be deleted. - func setColumnCount(ofTable table: Widget, to columns: Int) - /// Sets the contents of the table cell at the given position in a table. - func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) + /// Creates a single-child container with configurable padding. Used + /// to implement the ``View/padding(_:)`` and ``View/padding(_:_:)`` modifiers. + func createPaddingContainer(for child: Widget) -> Widget + /// Sets the padding of a padding container. + func setPadding( + ofPaddingContainer container: Widget, + top: Int, + bottom: Int, + leading: Int, + trailing: Int + ) + + /// Creates a single-child container which can control the styles of its child. + /// Used to implement style modifiers; i.e. ``View/foregroundColor(_:)``. + func createStyleContainer(for child: Widget) -> Widget + /// Sets the foreground color of a foreground color container. + func setForegroundColor(ofStyleContainer container: Widget, to color: Color) } extension AppBackend { @@ -328,17 +339,17 @@ extension AppBackend { } /// A helper to add multiple children to a layout-transparent container at once. - public func addChildren(_ children: [Widget], toPassthroughVStack container: Widget) { + public func addChildren(_ children: [Widget], toLayoutTransparentStack container: Widget) { for child in children { - addChild(child, toPassthroughVStack: container) + addChild(child, toLayoutTransparentStack: container) } } /// A helper to add multiple type-erased children to a layout-transparent container /// at once. Will crash if any of the widgets are for a different backend. - public func addChildren(_ children: [AnyWidget], toPassthroughVStack container: Widget) { + public func addChildren(_ children: [AnyWidget], toLayoutTransparentStack container: Widget) { for child in children { - addChild(child.into(), toPassthroughVStack: container) + addChild(child.into(), toLayoutTransparentStack: container) } } } @@ -350,7 +361,9 @@ extension AppBackend { Foundation.exit(1) } - public func createVStack(spacing: Int) -> Widget { + // MARK: Containers + + public func createVStack() -> Widget { todo() } public func addChild(_ child: Widget, toVStack container: Widget) { @@ -360,7 +373,7 @@ extension AppBackend { todo() } - public func createHStack(spacing: Int) -> Widget { + public func createHStack() -> Widget { todo() } public func addChild(_ child: Widget, toHStack container: Widget) { @@ -370,36 +383,23 @@ extension AppBackend { todo() } - public func createPassthroughVStack(spacing: Int) -> Widget { - todo() - } - public func addChild(_ child: Widget, toPassthroughVStack container: Widget) { + public func createSingleChildContainer() -> Widget { todo() } - public func updatePassthroughVStack(_ vStack: Widget) { + public func setChild(ofSingleChildContainer container: Widget, to widget: Widget?) { todo() } - public func createEitherContainer(initiallyContaining child: Widget?) -> Widget { + public func createLayoutTransparentStack() -> Widget { todo() } - public func setChild(ofEitherContainer container: Widget, to widget: Widget?) { + public func addChild(_ child: Widget, toLayoutTransparentStack container: Widget) { todo() } - - public func createPaddingContainer(for child: Widget) -> Widget { + public func removeChild(_ child: Widget, fromLayoutTransparentStack container: Widget) { todo() } - public func getChild(ofPaddingContainer container: Widget) -> Widget { - todo() - } - public func setPadding( - ofPaddingContainer container: Widget, - top: Int, - bottom: Int, - leading: Int, - trailing: Int - ) { + public func updateLayoutTransparentStack(_ container: Widget) { todo() } @@ -407,66 +407,31 @@ extension AppBackend { todo() } - public func createButton(label: String, action: @escaping () -> Void) -> Widget { - todo() - } - public func setLabel(ofButton button: Widget, to label: String) { - todo() - } - public func setAction(ofButton button: Widget, to action: @escaping () -> Void) { + public func createOneOfContainer() -> Widget { todo() } - - public func createToggle( - label: String, - active: Bool, - onChange: @escaping (Bool) -> Void - ) -> Widget { - todo("createToggle not implemented") - } - public func setIsActive(ofToggle toggle: Widget, to active: Bool) { - todo("setIsActive not implemented") - } - public func setOnChange(ofToggle toggle: Widget, to onChange: @escaping (Bool) -> Void) { - todo("setOnChange not implemented") - } - - public func createSwitch(active: Bool, onChange: @escaping (Bool) -> Void) -> Widget { - todo("createSwitch not implemented") - } - public func setIsActive(ofSwitch switchWidget: Widget, to active: Bool) { - todo("setIsActive not implemented") - } - public func setOnChange(ofSwitch switchWidget: Widget, to onChange: @escaping (Bool) -> Void) { - todo("setOnChange not implemented") - } - - public func createTextView(content: String, shouldWrap: Bool) -> Widget { + public func addChild(_ child: Widget, toOneOfContainer container: Widget) { todo() } - public func setContent(ofTextView textView: Widget, to content: String) { + public func removeChild(_ child: Widget, fromOneOfContainer container: Widget) { todo() } - public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) { + public func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) { todo() } - public func createImageView(filePath: String) -> Widget { - todo() - } - public func setFilePath(ofImageView imageView: Widget, to filePath: String) { + public func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget { todo() } - public func createSpacer( - expandHorizontally: Bool, expandVertically: Bool - ) -> Widget { - todo() - } - public func setExpandHorizontally(ofSpacer spacer: Widget, to expandHorizontally: Bool) { + // MARK: Layout + + public func createSpacer() -> Widget { todo() } - public func setExpandVertically(ofSpacer spacer: Widget, to expandVertically: Bool) { + public func updateSpacer( + _ spacer: Widget, expandHorizontally: Bool, expandVertically: Bool, minSize: Int + ) { todo() } @@ -474,124 +439,132 @@ extension AppBackend { todo() } - public func createSlider( - minimum: Double, - maximum: Double, - value: Double, - decimalPlaces: Int, - onChange: @escaping (Double) -> Void - ) -> Widget { - todo() - } - public func setMinimum(ofSlider slider: Widget, to minimum: Double) { + // MARK: Passive views + + public func createTextView(content: String, shouldWrap: Bool) -> Widget { todo() } - public func setMaximum(ofSlider slider: Widget, to maximum: Double) { + public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) { todo() } - public func setValue(ofSlider slider: Widget, to value: Double) { + + public func createImageView(filePath: String) -> Widget { todo() } - public func setDecimalPlaces(ofSlider slider: Widget, to decimalPlaces: Int) { + public func updateImageView(_ imageView: Widget, filePath: String) { todo() } - public func setOnChange(ofSlider slider: Widget, to onChange: @escaping (Double) -> Void) { + + public func createTable(rows: Int, columns: Int) -> Widget { todo() } - - public func createTextField( - content: String, placeholder: String, onChange: @escaping (String) -> Void - ) -> Widget { + public func setRowCount(ofTable table: Widget, to rows: Int) { todo() } - public func setContent(ofTextField textField: Widget, to content: String) { + public func setColumnCount(ofTable table: Widget, to columns: Int) { todo() } - public func setPlaceholder(ofTextField textField: Widget, to placeholder: String) { + public func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) { todo() } - public func setOnChange(ofTextField textField: Widget, to onChange: @escaping (String) -> Void) - { + + // MARK: Controls + + public func createButton() -> Widget { todo() } - public func getContent(ofTextField textField: Widget) -> String { + public func updateButton(_ button: Widget, label: String, action: @escaping () -> Void) { todo() } - public func createListView() -> Widget { + public func createToggle() -> Widget { todo() } - public func addChild(_ child: Widget, toListView listView: Widget) { + public func updateToggle(_ toggle: Widget, label: String, onChange: @escaping (Bool) -> Void) { todo() } - public func removeChild(_ child: Widget, fromListView listView: Widget) { + public func setState(ofToggle toggle: Widget, to state: Bool) { todo() } - // TODO: Perhaps all views should have this just in-case backends need to add additional logic? - public func updateListView(_ listView: Widget) { + public func createSwitch() -> Widget { todo() } - - public func createOneOfContainer() -> Widget { + public func updateSwitch( + _ switchWidget: Widget, onChange: @escaping (Bool) -> Void + ) { todo() } - public func addChild(_ child: Widget, toOneOfContainer container: Widget) { + public func setState(ofSwitch switchWidget: Widget, to state: Bool) { todo() } - public func removeChild(_ child: Widget, fromOneOfContainer container: Widget) { + + public func createSlider() -> Widget { todo() } - public func setVisibleChild(ofOneOfContainer container: Widget, to child: Widget) { + public func updateSlider( + _ slider: Widget, minimum: Double, maximum: Double, decimalPlaces: Int, + onChange: @escaping (Double) -> Void + ) { todo() } - - public func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget { + public func setValue(ofSlider slider: Widget, to value: Double) { todo() } - public func createPicker( - options: [String], selectedOption: Int?, onChange: @escaping (Int?) -> Void - ) -> Widget { + public func createTextField() -> Widget { todo() } - public func setOptions(ofPicker picker: Widget, to options: [String]) { + public func updateTextField( + _ textField: Widget, placeholder: String, onChange: @escaping (String) -> Void + ) { todo() } - public func setSelectedOption(ofPicker picker: Widget, to selectedOption: Int?) { + public func setContent(ofTextField textField: Widget, to content: String) { todo() } - public func setOnChange(ofPicker picker: Widget, to onChange: @escaping (Int?) -> Void) { + public func getContent(ofTextField textField: Widget) -> String { todo() } - public func createFrameContainer(for child: Widget, minWidth: Int, minHeight: Int) -> Widget { + public func createPicker() -> Widget { todo() } - public func setMinWidth(ofFrameContainer container: Widget, to minWidth: Int) { + public func updatePicker( + _ picker: Widget, options: [String], onChange: @escaping (Int?) -> Void + ) { todo() } - public func setMinHeight(ofFrameContainer container: Widget, to minHeight: Int) { + public func setSelectedOption(ofPicker picker: Widget, to selectedOption: Int?) { todo() } - public func createForegroundColorContainer(for child: Widget, color: Color) -> Widget { + // MARK: Modifiers + + public func createFrameContainer(for child: Widget) -> Widget { todo() } - public func setForegroundColor(ofForegroundColorContainer container: Widget, to color: Color) { + public func updateFrameContainer(_ container: Widget, minWidth: Int, minHeight: Int) { todo() } - public func createTable(rows: Int, columns: Int) -> Widget { + public func createPaddingContainer(for child: Widget) -> Widget { todo() } - public func setRowCount(ofTable table: Widget, to rows: Int) { + public func setPadding( + ofPaddingContainer container: Widget, + top: Int, + bottom: Int, + leading: Int, + trailing: Int + ) { todo() } - public func setColumnCount(ofTable table: Widget, to columns: Int) { + + public func createStyleContainer(for child: Widget) -> Widget { todo() } - public func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) { + public func setForegroundColor(ofStyleContainer container: Widget, to color: Color) { todo() } } diff --git a/Sources/SwiftCrossUI/Modifiers/ForegroundColor.swift b/Sources/SwiftCrossUI/Modifiers/ForegroundColor.swift index 1051e6dc..5c21f017 100644 --- a/Sources/SwiftCrossUI/Modifiers/ForegroundColor.swift +++ b/Sources/SwiftCrossUI/Modifiers/ForegroundColor.swift @@ -25,10 +25,7 @@ struct ForegroundView: TypeSafeView { _ children: ViewGraphNodeChildren1, backend: Backend ) -> Backend.Widget { - return backend.createForegroundColorContainer( - for: children.child0.widget.into(), - color: color - ) + return backend.createStyleContainer(for: children.child0.widget.into()) } func update( @@ -36,6 +33,6 @@ struct ForegroundView: TypeSafeView { children: ViewGraphNodeChildren1, backend: Backend ) { - backend.setForegroundColor(ofForegroundColorContainer: widget, to: color) + backend.setForegroundColor(ofStyleContainer: widget, to: color) } } diff --git a/Sources/SwiftCrossUI/Modifiers/Frame.swift b/Sources/SwiftCrossUI/Modifiers/Frame.swift index bec1fac8..6282c565 100644 --- a/Sources/SwiftCrossUI/Modifiers/Frame.swift +++ b/Sources/SwiftCrossUI/Modifiers/Frame.swift @@ -29,10 +29,7 @@ struct FrameView: TypeSafeView { _ children: ViewGraphNodeChildren1, backend: Backend ) -> Backend.Widget { - return backend.createFrameContainer( - for: children.child0.widget.into(), minWidth: minWidth, - minHeight: minHeight - ) + return backend.createFrameContainer(for: children.child0.widget.into()) } func update( @@ -40,7 +37,6 @@ struct FrameView: TypeSafeView { children: ViewGraphNodeChildren1, backend: Backend ) { - backend.setMinWidth(ofFrameContainer: widget, to: minWidth) - backend.setMinHeight(ofFrameContainer: widget, to: minHeight) + backend.updateFrameContainer(widget, minWidth: minWidth, minHeight: minHeight) } } diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift index aa8f1098..a9567d6c 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift @@ -14,9 +14,9 @@ extension ViewGraphNodeChildren { /// on the orientation of its parent). public func asSingleWidget(backend: Backend) -> Backend.Widget { let widgets: [Backend.Widget] = widgets.map { $0.into() } - let stack = backend.createPassthroughVStack(spacing: 0) + let stack = backend.createLayoutTransparentStack() for widget in widgets { - backend.addChild(widget, toPassthroughVStack: stack) + backend.addChild(widget, toLayoutTransparentStack: stack) } return stack } diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift.gyb b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift.gyb index 10731dd5..39bac055 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift.gyb +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNodeChildren.swift.gyb @@ -18,9 +18,9 @@ extension ViewGraphNodeChildren { /// on the orientation of its parent). public func asSingleWidget(backend: Backend) -> Backend.Widget { let widgets: [Backend.Widget] = widgets.map { $0.into() } - let stack = backend.createPassthroughVStack(spacing: 0) + let stack = backend.createLayoutTransparentStack() for widget in widgets { - backend.addChild(widget, toPassthroughVStack: stack) + backend.addChild(widget, toLayoutTransparentStack: stack) } return stack } diff --git a/Sources/SwiftCrossUI/Views/Button.swift b/Sources/SwiftCrossUI/Views/Button.swift index 6ec456ad..d834d326 100644 --- a/Sources/SwiftCrossUI/Views/Button.swift +++ b/Sources/SwiftCrossUI/Views/Button.swift @@ -14,14 +14,13 @@ public struct Button: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createButton(label: label, action: action) + return backend.createButton() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setLabel(ofButton: widget, to: label) - backend.setAction(ofButton: widget, to: action) + backend.updateButton(widget, label: label, action: action) } } diff --git a/Sources/SwiftCrossUI/Views/EitherView.swift b/Sources/SwiftCrossUI/Views/EitherView.swift index 0c14fc02..8a40528b 100644 --- a/Sources/SwiftCrossUI/Views/EitherView.swift +++ b/Sources/SwiftCrossUI/Views/EitherView.swift @@ -33,14 +33,16 @@ public struct EitherView: TypeSafeView, View { func asWidget( _ children: EitherViewChildren, backend: Backend ) -> Backend.Widget { - return backend.createEitherContainer(initiallyContaining: children.widget(for: backend)) + let container = backend.createSingleChildContainer() + backend.setChild(ofSingleChildContainer: container, to: children.widget(for: backend)) + return container } func update( _ widget: Backend.Widget, children: EitherViewChildren, backend: Backend ) { if children.hasSwitchedCase { - backend.setChild(ofEitherContainer: widget, to: children.widget(for: backend)) + backend.setChild(ofSingleChildContainer: widget, to: children.widget(for: backend)) } } } diff --git a/Sources/SwiftCrossUI/Views/ForEach.swift b/Sources/SwiftCrossUI/Views/ForEach.swift index 7bd4696a..ce3bd8e3 100644 --- a/Sources/SwiftCrossUI/Views/ForEach.swift +++ b/Sources/SwiftCrossUI/Views/ForEach.swift @@ -74,7 +74,7 @@ class ForEachViewChildren< /// Gets a variable length view's children as view graph node children. init(from view: ForEach, backend: Backend) { - let container = backend.createListView() + let container = backend.createLayoutTransparentStack() self.container = AnyWidget(container) nodes = view.elements @@ -85,14 +85,14 @@ class ForEachViewChildren< .map(AnyViewGraphNode.init(_:)) for node in nodes { - backend.addChild(node.widget.into(), toListView: container) + backend.addChild(node.widget.into(), toLayoutTransparentStack: container) } } /// Updates the variable length list widget with a new instance of the for each view being /// represented. func update(with view: ForEach, backend: Backend) { - backend.updateListView(container.into()) + backend.updateLayoutTransparentStack(container.into()) // TODO: The way we're reusing nodes for technically different elements means that if // Child has state of its own then it could get pretty confused thinking that its state @@ -115,14 +115,14 @@ class ForEachViewChildren< backend: backend ) nodes.append(node) - backend.addChild(node.widget.into(), toListView: container.into()) + backend.addChild(node.widget.into(), toLayoutTransparentStack: container.into()) } } else if remaining < 0 { let unused = -remaining for i in (nodes.count - unused)..: View { _ children: any ViewGraphNodeChildren, backend: Backend ) -> Backend.Widget { - let hStack = backend.createHStack(spacing: spacing) + let hStack = backend.createHStack() backend.addChildren(children.widgets(for: backend), toHStack: hStack) return hStack } diff --git a/Sources/SwiftCrossUI/Views/Image.swift b/Sources/SwiftCrossUI/Views/Image.swift index d326d17b..a606a1a7 100644 --- a/Sources/SwiftCrossUI/Views/Image.swift +++ b/Sources/SwiftCrossUI/Views/Image.swift @@ -20,6 +20,6 @@ public struct Image: ElementaryView, View { _ widget: Backend.Widget, backend: Backend ) { - backend.setFilePath(ofImageView: widget, to: path) + backend.updateImageView(widget, filePath: path) } } diff --git a/Sources/SwiftCrossUI/Views/OptionalView.swift b/Sources/SwiftCrossUI/Views/OptionalView.swift index 215a24a6..bb6f4f15 100644 --- a/Sources/SwiftCrossUI/Views/OptionalView.swift +++ b/Sources/SwiftCrossUI/Views/OptionalView.swift @@ -24,16 +24,16 @@ public struct OptionalView: TypeSafeView, View { func asWidget( _ children: OptionalViewChildren, backend: Backend ) -> Backend.Widget { - return backend.createEitherContainer( - initiallyContaining: children.widget(for: backend) - ) + let container = backend.createSingleChildContainer() + backend.setChild(ofSingleChildContainer: container, to: children.widget(for: backend)) + return container } func update( _ widget: Backend.Widget, children: OptionalViewChildren, backend: Backend ) { if children.hasToggled { - backend.setChild(ofEitherContainer: widget, to: children.widget(for: backend)) + backend.setChild(ofSingleChildContainer: widget, to: children.widget(for: backend)) } } } diff --git a/Sources/SwiftCrossUI/Views/Picker.swift b/Sources/SwiftCrossUI/Views/Picker.swift index 2483d8da..289ce23d 100644 --- a/Sources/SwiftCrossUI/Views/Picker.swift +++ b/Sources/SwiftCrossUI/Views/Picker.swift @@ -21,22 +21,19 @@ public struct Picker: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createPicker( - options: options.map { "\($0)" }, - selectedOption: selectedOptionIndex - ) { selectedIndex in + return backend.createPicker() + } + + public func update( + _ widget: Backend.Widget, backend: Backend + ) { + backend.updatePicker(widget, options: options.map { "\($0)" }) { selectedIndex in guard let selectedIndex = selectedIndex else { value.wrappedValue = nil return } value.wrappedValue = options[selectedIndex] } - } - - public func update( - _ widget: Backend.Widget, backend: Backend - ) { - backend.setOptions(ofPicker: widget, to: options.map { "\($0)" }) backend.setSelectedOption(ofPicker: widget, to: selectedOptionIndex) } } diff --git a/Sources/SwiftCrossUI/Views/ScrollView.swift b/Sources/SwiftCrossUI/Views/ScrollView.swift index 13bdef72..0a0814f9 100644 --- a/Sources/SwiftCrossUI/Views/ScrollView.swift +++ b/Sources/SwiftCrossUI/Views/ScrollView.swift @@ -12,7 +12,7 @@ public struct ScrollView: View { _ children: any ViewGraphNodeChildren, backend: Backend ) -> Backend.Widget { - let vStack = backend.createVStack(spacing: 0) + let vStack = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: vStack) return backend.createScrollContainer(for: vStack) diff --git a/Sources/SwiftCrossUI/Views/Slider.swift b/Sources/SwiftCrossUI/Views/Slider.swift index db72b3c1..3692a91a 100644 --- a/Sources/SwiftCrossUI/Views/Slider.swift +++ b/Sources/SwiftCrossUI/Views/Slider.swift @@ -89,27 +89,16 @@ public struct Slider: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createSlider( - minimum: minimum, - maximum: maximum, - value: value?.wrappedValue ?? minimum, - decimalPlaces: decimalPlaces - ) { [weak value] newValue in - guard let value = value else { - return - } - value.wrappedValue = newValue - } + return backend.createSlider() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setMinimum(ofSlider: widget, to: minimum) - backend.setMaximum(ofSlider: widget, to: maximum) - backend.setDecimalPlaces(ofSlider: widget, to: decimalPlaces) - backend.setOnChange(ofSlider: widget) { [weak value] newValue in + backend.updateSlider( + widget, minimum: minimum, maximum: maximum, decimalPlaces: decimalPlaces + ) { [weak value] newValue in guard let value = value else { return } diff --git a/Sources/SwiftCrossUI/Views/Spacer.swift b/Sources/SwiftCrossUI/Views/Spacer.swift index 422844d2..9c4fffaa 100644 --- a/Sources/SwiftCrossUI/Views/Spacer.swift +++ b/Sources/SwiftCrossUI/Views/Spacer.swift @@ -14,46 +14,19 @@ public struct Spacer: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - let spacer = backend.createSpacer(expandHorizontally: false, expandVertically: false) - return backend.createPaddingContainer(for: spacer) + return backend.createSpacer() } public func update( _ widget: Backend.Widget, backend: Backend ) { - let spacer = backend.getChild(ofPaddingContainer: widget) - switch backend.getInheritedOrientation(of: widget) { - case .horizontal: - backend.setExpandHorizontally(ofSpacer: spacer, to: true) - backend.setExpandVertically(ofSpacer: spacer, to: false) - backend.setPadding( - ofPaddingContainer: widget, - top: 0, - bottom: 0, - leading: minLength ?? 0, - trailing: 0 - ) - case .vertical: - backend.setExpandHorizontally(ofSpacer: spacer, to: false) - backend.setExpandVertically(ofSpacer: spacer, to: true) - backend.setPadding( - ofPaddingContainer: widget, - top: minLength ?? 0, - bottom: 0, - leading: 0, - trailing: 0 - ) - case nil: - backend.setExpandHorizontally(ofSpacer: spacer, to: true) - backend.setExpandVertically(ofSpacer: spacer, to: true) - backend.setPadding( - ofPaddingContainer: widget, - top: minLength ?? 0, - bottom: 0, - leading: minLength ?? 0, - trailing: 0 - ) - } + let orientation = backend.getInheritedOrientation(of: widget) + backend.updateSpacer( + widget, + expandHorizontally: orientation == .vertical || orientation == nil, + expandVertically: orientation == .vertical || orientation == nil, + minSize: minLength ?? 0 + ) } } diff --git a/Sources/SwiftCrossUI/Views/Table.swift b/Sources/SwiftCrossUI/Views/Table.swift index 476913fc..c69143c3 100644 --- a/Sources/SwiftCrossUI/Views/Table.swift +++ b/Sources/SwiftCrossUI/Views/Table.swift @@ -25,24 +25,22 @@ public struct Table: ElementaryView, View { backend.setRowCount(ofTable: widget, to: rows.count + 1) backend.setColumnCount(ofTable: widget, to: columns.count) for (i, column) in columns.enumerated() { + let textView = backend.createTextView() + backend.updateTextView(textView, content: column.title, shouldWrap: false) backend.setCell( at: CellPosition(0, i), inTable: widget, - to: backend.createTextView( - content: column.title, - shouldWrap: false - ) + to: textView ) } for (i, row) in rows.enumerated() { for (j, column) in columns.enumerated() { + let textView = backend.createTextView() + backend.updateTextView(textView, content: column.value(row), shouldWrap: false) backend.setCell( at: CellPosition(i + 1, j), inTable: widget, - to: backend.createTextView( - content: column.value(row), - shouldWrap: false - ) + to: textView ) } } diff --git a/Sources/SwiftCrossUI/Views/Text.swift b/Sources/SwiftCrossUI/Views/Text.swift index d482f508..284b1dc7 100644 --- a/Sources/SwiftCrossUI/Views/Text.swift +++ b/Sources/SwiftCrossUI/Views/Text.swift @@ -14,14 +14,13 @@ public struct Text: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createTextView(content: string, shouldWrap: wrap) + return backend.createTextView() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setContent(ofTextView: widget, to: string) - backend.setWrap(ofTextView: widget, to: wrap) + backend.updateTextView(widget, content: string, shouldWrap: wrap) } } diff --git a/Sources/SwiftCrossUI/Views/TextField.swift b/Sources/SwiftCrossUI/Views/TextField.swift index 99ba9144..bebb0985 100644 --- a/Sources/SwiftCrossUI/Views/TextField.swift +++ b/Sources/SwiftCrossUI/Views/TextField.swift @@ -14,25 +14,18 @@ public struct TextField: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createTextField( - content: value?.wrappedValue ?? "", - placeholder: placeholder, - onChange: { newValue in - self.value?.wrappedValue = newValue - } - ) + return backend.createTextField() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setPlaceholder(ofTextField: widget, to: placeholder) + backend.updateTextField(widget, placeholder: placeholder) { newValue in + self.value?.wrappedValue = newValue + } if let value = value?.wrappedValue, value != backend.getContent(ofTextField: widget) { backend.setContent(ofTextField: widget, to: value) } - backend.setOnChange(ofTextField: widget) { newValue in - self.value?.wrappedValue = newValue - } } } diff --git a/Sources/SwiftCrossUI/Views/ToggleButton.swift b/Sources/SwiftCrossUI/Views/ToggleButton.swift index 7fafa653..e6581870 100644 --- a/Sources/SwiftCrossUI/Views/ToggleButton.swift +++ b/Sources/SwiftCrossUI/Views/ToggleButton.swift @@ -14,23 +14,16 @@ struct ToggleButton: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createToggle( - label: label, - active: active.wrappedValue, - onChange: { newValue in - self.active.wrappedValue = newValue - } - ) + return backend.createToggle() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setLabel(ofButton: widget, to: label) - backend.setIsActive(ofToggle: widget, to: active.wrappedValue) - backend.setOnChange(ofToggle: widget) { newActiveState in + backend.updateToggle(widget, label: label) { newActiveState in active.wrappedValue = newActiveState } + backend.setState(ofToggle: widget, to: active.wrappedValue) } } diff --git a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift index 2c905b55..dbe2b8c6 100644 --- a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift +++ b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift @@ -11,21 +11,16 @@ struct ToggleSwitch: ElementaryView, View { public func asWidget( backend: Backend ) -> Backend.Widget { - return backend.createSwitch( - active: active.wrappedValue, - onChange: { newValue in - self.active.wrappedValue = newValue - } - ) + return backend.createSwitch() } public func update( _ widget: Backend.Widget, backend: Backend ) { - backend.setIsActive(ofSwitch: widget, to: active.wrappedValue) - backend.setOnChange(ofSwitch: widget) { newActiveState in + backend.updateSwitch(widget) { newActiveState in active.wrappedValue = newActiveState } + backend.setState(ofSwitch: widget, to: active.wrappedValue) } } diff --git a/Sources/SwiftCrossUI/Views/VStack.swift b/Sources/SwiftCrossUI/Views/VStack.swift index 959e5bc8..909202db 100644 --- a/Sources/SwiftCrossUI/Views/VStack.swift +++ b/Sources/SwiftCrossUI/Views/VStack.swift @@ -15,7 +15,7 @@ public struct VStack: View { _ children: any ViewGraphNodeChildren, backend: Backend ) -> Backend.Widget { - let vStack = backend.createVStack(spacing: spacing) + let vStack = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: vStack) return vStack } diff --git a/Sources/SwiftCrossUI/Views/VariadicView.swift b/Sources/SwiftCrossUI/Views/VariadicView.swift index dc6422a7..4cd1b4b3 100644 --- a/Sources/SwiftCrossUI/Views/VariadicView.swift +++ b/Sources/SwiftCrossUI/Views/VariadicView.swift @@ -29,7 +29,7 @@ public struct VariadicView1: TypeSafeView, View { } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -73,7 +73,7 @@ public struct VariadicView2: TypeSafeView, View { } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -121,7 +121,7 @@ public struct VariadicView3: TypeSafeView } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -174,7 +174,7 @@ public struct VariadicView4: } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -232,7 +232,7 @@ public struct VariadicView5(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -297,7 +297,7 @@ public struct VariadicView6< } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -366,7 +366,7 @@ public struct VariadicView7< } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -442,7 +442,7 @@ public struct VariadicView8< } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -522,7 +522,7 @@ public struct VariadicView9< } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } @@ -606,7 +606,7 @@ public struct VariadicView10< } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } diff --git a/Sources/SwiftCrossUI/Views/VariadicView.swift.gyb b/Sources/SwiftCrossUI/Views/VariadicView.swift.gyb index 6be948b0..d0c7edbd 100644 --- a/Sources/SwiftCrossUI/Views/VariadicView.swift.gyb +++ b/Sources/SwiftCrossUI/Views/VariadicView.swift.gyb @@ -47,7 +47,7 @@ public struct ${view}<${struct_parameters}>: TypeSafeView, View { } func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { - let container = backend.createVStack(spacing: 0) + let container = backend.createVStack() backend.addChildren(children.widgets(for: backend), toVStack: container) return container } diff --git a/Sources/SwiftCrossUI/Views/View.swift b/Sources/SwiftCrossUI/Views/View.swift index c24c09e8..fe5fd86f 100644 --- a/Sources/SwiftCrossUI/Views/View.swift +++ b/Sources/SwiftCrossUI/Views/View.swift @@ -65,7 +65,8 @@ extension View { _ children: any ViewGraphNodeChildren, backend: Backend ) -> Backend.Widget { - let vStack = backend.createVStack(spacing: 8) + let vStack = backend.createVStack() + backend.setSpacing(ofVStack: vStack, to: 8) backend.addChildren(children.widgets(for: backend), toVStack: vStack) return vStack }