Add support for carousels

Add support for any Libadwaita insertable container
This commit is contained in:
david-swift 2024-01-01 20:33:06 +01:00
parent 1c50b3b923
commit 8eb004c9ea
8 changed files with 293 additions and 16 deletions

View File

@ -18,7 +18,9 @@
- [AppearObserver](structs/AppearObserver.md)
- [Binding](structs/Binding.md)
- [Button](structs/Button.md)
- [Carousel](structs/Carousel.md)
- [Clamp](structs/Clamp.md)
- [Container](structs/Container.md)
- [ContentModifier](structs/ContentModifier.md)
- [FileDialog](structs/FileDialog.md)
- [HStack](structs/HStack.md)

View File

@ -0,0 +1,26 @@
**STRUCT**
# `Carousel`
A carousel view.
## Properties
### `elements`
The elements.
### `content`
The content.
### `view`
The view.
## Methods
### `init(_:content:)`
Initialize `Carousel`.
- Parameters:
- elements: The elements.
- content: The view for an element.

View File

@ -0,0 +1,60 @@
**STRUCT**
# `Container`
A container widget.
## Properties
### `elements`
The elements.
### `content`
The content.
### `container`
Get the container for initialization.
### `elementsID`
The identifier of the elements storage.
## Methods
### `init(_:content:container:)`
Initialize `Container`.
- Parameters:
- elements: The elements.
- content: The view for an element.
- container: Get the initial Libadwaita container widget.
### `update(_:modifiers:)`
Update a view storage.
- Parameters:
- storage: The view storage.
- modifiers: Modify views before being updated.
### `container(modifiers:)`
Get a view storage.
- Parameter modifiers: Modify views before being updated.
- Returns: The view storage.
### `updateContainer(_:content:modifiers:)`
Update the container's content.
- Parameters:
- container: The container.
- content: The content's view storage.
- modifiers: The view modifiers.
### `getWidget(element:modifiers:)`
Get the view storage of an element.
- Parameters:
- element: The element.
- modifiers: The modifiers.
- Returns: The view storage.

View File

@ -0,0 +1,37 @@
//
// Carousel.swift
// Adwaita
//
// Created by david-swift on 01.01.24.
//
import Libadwaita
/// A carousel view.
public struct Carousel<Element>: View where Element: Identifiable {
/// The elements.
var elements: [Element]
/// The content.
var content: (Element) -> Body
/// The view.
public var view: Body {
Container(elements, content: content) {
Libadwaita.Carousel()
}
}
/// Initialize `Carousel`.
/// - Parameters:
/// - elements: The elements.
/// - content: The view for an element.
public init(
_ elements: [Element],
@ViewBuilder content: @escaping (Element) -> Body
) {
self.content = content
self.elements = elements
}
}

View File

@ -0,0 +1,101 @@
//
// Container.swift
// Adwaita
//
// Created by david-swift on 01.01.24.
//
import LevenshteinTransformations
import Libadwaita
/// A container widget.
public struct Container<Type, Element>: Widget
where Element: Identifiable, Type: InsertableContainer, Type: NativeWidgetPeer {
/// The elements.
var elements: [Element]
/// The content.
var content: (Element) -> Body
/// Get the container for initialization.
var container: () -> Type
/// The identifier of the elements storage.
let elementsID = "elements"
/// Initialize `Container`.
/// - Parameters:
/// - elements: The elements.
/// - content: The view for an element.
/// - container: Get the initial Libadwaita container widget.
public init(
_ elements: [Element],
@ViewBuilder content: @escaping (Element) -> Body,
container: @escaping () -> Type
) {
self.content = content
self.elements = elements
self.container = container
}
/// Update a view storage.
/// - Parameters:
/// - storage: The view storage.
/// - modifiers: Modify views before being updated.
public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) {
if let container = storage.view as? Type {
var content: [ViewStorage] = storage.content[.mainContent] ?? []
updateContainer(container, content: .init { content } set: { content = $0 }, modifiers: modifiers)
storage.content[.mainContent] = content
for (index, element) in elements.enumerated() {
self.content(element).widget(modifiers: modifiers).update(content[index], modifiers: modifiers)
}
}
}
/// Get a view storage.
/// - Parameter modifiers: Modify views before being updated.
/// - Returns: The view storage.
public func container(modifiers: [(View) -> View]) -> ViewStorage {
let container = self.container()
var content: [ViewStorage] = []
updateContainer(container, content: .init { content } set: { content = $0 }, modifiers: modifiers)
return .init(container, content: [.mainContent: content])
}
/// Update the container's content.
/// - Parameters:
/// - container: The container.
/// - content: The content's view storage.
/// - modifiers: The view modifiers.
func updateContainer(_ container: Type, content: Binding<[ViewStorage]>, modifiers: [(View) -> View]) {
let old = container.fields[elementsID] as? [Element] ?? []
old.identifiableTransform(
to: elements,
functions: .init { index, element in
let widget = getWidget(element: element, modifiers: modifiers)
_ = container.removeWidgets([content.wrappedValue[index].view])
_ = container.insert(widget.view, at: index)
content.wrappedValue.remove(at: index)
content.wrappedValue.insert(widget, at: index)
} delete: { index in
_ = container.removeWidgets([content.wrappedValue[index].view])
content.wrappedValue.remove(at: index)
} insert: { index, element in
let widget = getWidget(element: element, modifiers: modifiers)
_ = container.insert(widget.view, at: index)
content.wrappedValue.insert(widget, at: index)
}
)
container.fields[elementsID] = elements
}
/// Get the view storage of an element.
/// - Parameters:
/// - element: The element.
/// - modifiers: The modifiers.
/// - Returns: The view storage.
func getWidget(element: Element, modifiers: [(View) -> View]) -> ViewStorage {
self.content(element).widget(modifiers: modifiers).container(modifiers: modifiers)
}
}

44
Tests/CarouselDemo.swift Normal file
View File

@ -0,0 +1,44 @@
//
// CarouselDemo.swift
// Adwaita
//
// Created by david-swift on 01.01.24.
//
// swiftlint:disable missing_docs no_magic_numbers
import Adwaita
import Foundation
struct CarouselDemo: View {
@State private var items: [ListDemo.Element] = [.init(id: "Hello"), .init(id: "World")]
var view: Body {
Button("Add Card") {
let element = ListDemo.Element(id: UUID().uuidString)
items.append(element)
}
.padding()
.halign(.center)
Carousel(items) { element in
VStack {
Text(element.id)
.vexpand()
Button("Delete") {
items = items.filter { $0.id != element.id }
}
.padding()
}
.vexpand()
.hexpand()
.style("card")
.padding(20)
.frame(minWidth: 300, minHeight: 200)
.frame(maxSize: 500)
}
}
}
// swiftlint:enable missing_docs no_magic_numbers

View File

@ -21,6 +21,7 @@ enum Page: String, Identifiable, CaseIterable {
case overlayWindow
case toast
case list
case carousel
var id: Self {
self
@ -64,6 +65,8 @@ enum Page: String, Identifiable, CaseIterable {
return "Show a notification inside of your app."
case .list:
return "Organize content in multiple rows."
case .carousel:
return "Scroll horizontally on a touchpad or touchscreen, or scroll down on your mouse wheel."
}
}
@ -88,6 +91,8 @@ enum Page: String, Identifiable, CaseIterable {
ToastDemo(toast: toast)
case .list:
ListDemo()
case .carousel:
CarouselDemo()
}
}

View File

@ -2,22 +2,24 @@
This is an overview of the available widgets and other components in _Adwaita_.
| Name | Description | Widget |
| -------------------- | ----------------------------------------------------------------- | ---------------------- |
| Button | A widget that triggers a function when being clicked. | GtkButton |
| ViewStack | A widget that displays one of its child views based on an id. | GtkStack |
| HeaderBar | A widget for creating custom title bars for windows. | GtkHeaderBar |
| Text | A widget for displaying a small amount of text. | GtkLabel |
| VStack | A widget which arranges child widgets into a single column. | GtkBox |
| 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 |
| Menu | A widget showing a button that toggles the appearance of a menu. | GtkMenuButton |
| NavigationSplitView | A widget presenting sidebar and content side by side. | AdwNavigationSplitView |
| OverlaySplitView | A widget presenting sidebar and content side by side. | AdwOverlaySplitView |
| ScrollView | A container that makes its child scrollable. | GtkScrolledWindow |
| StatusPage | A page with an icon, title, and optionally description and widget.| AdwStatusPage |
| StateWrapper | A wrapper not affecting the UI which stores state information. | - |
| Name | Description | Widget |
| -------------------- | ------------------------------------------------------------------- | ---------------------- |
| Button | A widget that triggers a function when being clicked. | GtkButton |
| ViewStack | A widget that displays one of its child views based on an id. | GtkStack |
| HeaderBar | A widget for creating custom title bars for windows. | GtkHeaderBar |
| Text | A widget for displaying a small amount of text. | GtkLabel |
| VStack | A widget which arranges child widgets into a single column. | GtkBox |
| 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 |
| Menu | A widget showing a button that toggles the appearance of a menu. | GtkMenuButton |
| NavigationSplitView | A widget presenting sidebar and content side by side. | AdwNavigationSplitView |
| OverlaySplitView | A widget presenting sidebar and content side by side. | AdwOverlaySplitView |
| ScrollView | A container that makes its child scrollable. | GtkScrolledWindow |
| StatusPage | A page with an icon, title, and optionally description and widget. | AdwStatusPage |
| Container | Supports any widget conforming to `Libadwaita.InsertableContainer`. | Multiple widgets |
| Carousel | A paginated scrolling widget. | AdwCarousel |
| StateWrapper | A wrapper not affecting the UI which stores state information. | - |
### View Modifiers