Skip to content

Commit

Permalink
Add ForEach widget for dynamic GtkBox
Browse files Browse the repository at this point in the history
  • Loading branch information
david-swift committed Jan 30, 2024
1 parent 8d05d22 commit 81e489c
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions Documentation/Reference/README.md
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions 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.
80 changes: 80 additions & 0 deletions 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<Element>: 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<GtkBox>? = 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)
}
}

}
3 changes: 2 additions & 1 deletion 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 |
Expand All @@ -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 |
Expand Down

0 comments on commit 81e489c

Please sign in to comment.