diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index 21f4cfd..a791c1e 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -34,6 +34,7 @@ - [EntryRow](structs/EntryRow.md) - [ExpanderRow](structs/ExpanderRow.md) - [FileDialog](structs/FileDialog.md) +- [ForEach](structs/ForEach.md) - [Form](structs/Form.md) - [HStack](structs/HStack.md) - [HeaderBar](structs/HeaderBar.md) diff --git a/Documentation/Reference/structs/ForEach.md b/Documentation/Reference/structs/ForEach.md new file mode 100644 index 0000000..ac41cce --- /dev/null +++ b/Documentation/Reference/structs/ForEach.md @@ -0,0 +1,37 @@ +**STRUCT** + +# `ForEach` + +A dynamic list but without a list design in the user interface. + +## Properties +### `elements` + +The dynamic widget elements. + +### `content` + +The dynamic widget content. + +### `horizontal` + +Whether the list is horizontal. + +## Methods +### `init(_:horizontal:content:)` + +Initialize `ForEach`. + +### `container(modifiers:)` + +Get the widget's view storage. +- Parameter modifiers: The view modifiers. +- Returns: The view storage. + +### `update(_:modifiers:updateProperties:)` + +Update the widget's view storage. +- Parameters: + - storage: The view storage. + - modifiers: The view modifiers. + - updateProperties: Whether to update the view's properties. diff --git a/Sources/Adwaita/View/ForEach.swift b/Sources/Adwaita/View/ForEach.swift new file mode 100644 index 0000000..e4e3d1d --- /dev/null +++ b/Sources/Adwaita/View/ForEach.swift @@ -0,0 +1,80 @@ +// +// ForEach.swift +// Adwaita +// +// Created by david-swift on 30.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A dynamic list but without a list design in the user interface. +public struct ForEach: Widget where Element: Identifiable { + + /// The dynamic widget elements. + var elements: [Element] + /// The dynamic widget content. + var content: (Element) -> Body + /// Whether the list is horizontal. + var horizontal: Bool + + /// Initialize `ForEach`. + public init(_ elements: [Element], horizontal: Bool = false, @ViewBuilder content: @escaping (Element) -> Body) { + self.elements = elements + self.content = content + self.horizontal = horizontal + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage( + gtk_box_new(horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, 0)?.opaque() + ) + update(storage, modifiers: modifiers, updateProperties: true) + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + /// - updateProperties: Whether to update the view's properties. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View], updateProperties: Bool) { + var contentStorage: [ViewStorage] = storage.content[.mainContent] ?? [] + let old = storage.fields["element"] as? [Element] ?? [] + let widget: UnsafeMutablePointer? = storage.pointer?.cast() + old.identifiableTransform( + to: elements, + functions: .init { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + gtk_box_remove(widget, contentStorage[safe: index]?.pointer?.cast()) + gtk_box_insert_child_after(widget, child.pointer?.cast(), contentStorage[safe: index]?.pointer?.cast()) + contentStorage.remove(at: index) + contentStorage.insert(child, at: index) + } delete: { index in + gtk_box_remove(widget, contentStorage[safe: index]?.pointer?.cast()) + contentStorage.remove(at: index) + } insert: { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + gtk_box_insert_child_after(widget, child.pointer?.cast(), contentStorage[safe: index]?.pointer?.cast()) + contentStorage.insert(child, at: index) + } + ) + if updateProperties { + gtk_orientable_set_orientation( + widget?.opaque(), + horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL + ) + } + storage.fields["element"] = elements + storage.content[.mainContent] = contentStorage + for (index, element) in elements.enumerated() { + content(element) + .widget(modifiers: modifiers) + .update(contentStorage[index], modifiers: modifiers, updateProperties: updateProperties) + } + } + +} diff --git a/user-manual/Information/Widgets.md b/user-manual/Information/Widgets.md index b443ee9..e484527 100644 --- a/user-manual/Information/Widgets.md +++ b/user-manual/Information/Widgets.md @@ -1,6 +1,6 @@ # Widgets -This is an overview of the available widgets and other components in _Adwaita_ that are not auto-generated. +This is an overview of the available widgets and other components in _Adwaita_ that are not auto-generated or that are wrappers for easily accessing auto-generated widgets. There are many more widgets available using auto-generation. Learn [how to use them.](AutoGeneratedWidgets.md) | Name | Description | Widget | @@ -10,6 +10,7 @@ There are many more widgets available using auto-generation. Learn [how to use t | HStack | A widget which arranges child widgets into a single row. | GtkBox | | Toggle | A button with two possible states, on and off. | GtkToggleButton | | List | A widget which arranges child widgets vertically into rows. | GtkListBox | +| ForEach | Arrange dynamic widgets vertically or horizontally. | GtkBox | | NavigationSplitView | A widget presenting sidebar and content side by side. | AdwNavigationSplitView | | ScrollView | A container that makes its child scrollable. | GtkScrolledWindow | | ViewSwitcher | A control for switching between different views. | AdwViewSwitcher |