Use the new wrapModifier for simple modifiers
Some checks are pending
Deploy Docs / publish (push) Waiting to run
SwiftLint / SwiftLint (push) Waiting to run

This commit is contained in:
david-swift 2026-02-03 00:07:26 +01:00
parent 8f6a83dfc3
commit d0d7d2b6d3
7 changed files with 129 additions and 286 deletions

View File

@ -5,6 +5,7 @@
// Created by david-swift on 16.10.24.
//
import CAdw
import Foundation
extension AnyView {
@ -243,90 +244,6 @@ extension AnyView {
.maximumSize(maxHeight ?? -1)
}
/// Add padding around a view.
/// - Parameters:
/// - padding: The size of the padding.
/// - edges: The edges which are affected by the padding.
/// - Returns: A view.
public func padding(_ padding: Int = 10, _ edges: Set<Edge> = .all) -> AnyView {
ModifierWrapper(content: self, padding: padding, edges: edges)
}
/// Add a padding of 10 around a view.
/// - Parameters:
/// - edges: The edges which are affected by the padding.
/// - Returns: A view.
public func padding(_ edges: Set<Edge> = .all) -> AnyView {
padding(10, edges)
}
/// Enable or disable the horizontal expansion.
/// - Parameter enabled: Whether it is enabled or disabled.
/// - Returns: A view.
public func hexpand(_ enabled: Bool = true) -> AnyView {
ModifierWrapper(content: self, hexpand: enabled)
}
/// Enable or disable the vertical expansion.
/// - Parameter enabled: Whether it is enabled or disabled.
/// - Returns: A view.
public func vexpand(_ enabled: Bool = true) -> AnyView {
ModifierWrapper(content: self, vexpand: enabled)
}
/// Set the horizontal alignment.
/// - Parameter align: The alignment.
/// - Returns: A view.
public func halign(_ align: Alignment) -> AnyView {
ModifierWrapper(content: self, halign: align)
}
/// Set the vertical alignment.
/// - Parameter align: The alignment.
/// - Returns: A view.
public func valign(_ align: Alignment) -> AnyView {
ModifierWrapper(content: self, valign: align)
}
/// Set the view's minimal width or height.
/// - Parameters:
/// - minWidth: The minimal width.
/// - minHeight: The minimal height.
/// - Returns: A view.
public func frame(minWidth: Int? = nil, minHeight: Int? = nil) -> AnyView {
ModifierWrapper(content: self, minWidth: minWidth, minHeight: minHeight)
}
/// Add a style class to the view.
/// - Parameters:
/// - style: The style class.
/// - active: Whether the style is currently applied.
/// - Returns: A view.
public func style(_ style: String, active: Bool = true) -> AnyView {
ModifierWrapper(content: self, style: style, styleActive: active)
}
/// Make the view insensitive (useful e.g. in overlays).
/// - Parameter insensitive: Whether the view is insensitive.
/// - Returns: A view.
public func insensitive(_ insensitive: Bool = true) -> AnyView {
ModifierWrapper(content: self, insensitive: insensitive)
}
/// Set the view's visibility.
/// - Parameter visible: Whether the view is visible.
/// - Returns: A view.
public func visible(_ visible: Bool = true) -> AnyView {
ModifierWrapper(content: self, visible: visible)
}
/// Add a tooltip to the widget.
/// - Parameter tooltip: The tooltip text.
/// - Returns: A view.
public func tooltip(_ tooltip: String) -> AnyView {
ModifierWrapper(content: self, tooltip: tooltip)
}
/// Present a toast when the signal gets activated.
/// - Parameters:
/// - title: The title of the toast.

View File

@ -0,0 +1,123 @@
//
// AnyView+wrapModifier.swift
// Adwaita
//
// Created by david-swift on 02.02.26.
//
import CAdw
extension AnyView {
/// Add padding around a view.
/// - Parameters:
/// - padding: The size of the padding.
/// - edges: The edges which are affected by the padding.
/// - Returns: A view.
public func padding(_ padding: Int = 10, _ edges: Set<Edge> = .all) -> AnyView {
wrapModifier(properties: [padding, edges]) { storage in
if edges.contains(.leading) { gtk_widget_set_margin_start(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.trailing) { gtk_widget_set_margin_end(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.top) { gtk_widget_set_margin_top(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.bottom) { gtk_widget_set_margin_bottom(storage.opaquePointer?.cast(), padding.cInt) }
}
}
/// Add a padding of 10 around a view.
/// - Parameters:
/// - edges: The edges which are affected by the padding.
/// - Returns: A view.
public func padding(_ edges: Set<Edge> = .all) -> AnyView {
padding(10, edges)
}
/// Enable or disable the horizontal expansion.
/// - Parameter enabled: Whether it is enabled or disabled.
/// - Returns: A view.
public func hexpand(_ enabled: Bool = true) -> AnyView {
wrapModifier(properties: [enabled]) { storage in
gtk_widget_set_hexpand(storage.opaquePointer?.cast(), enabled.cBool)
}
}
/// Enable or disable the vertical expansion.
/// - Parameter enabled: Whether it is enabled or disabled.
/// - Returns: A view.
public func vexpand(_ enabled: Bool = true) -> AnyView {
wrapModifier(properties: [enabled]) { storage in
gtk_widget_set_vexpand(storage.opaquePointer?.cast(), enabled.cBool)
}
}
/// Set the horizontal alignment.
/// - Parameter align: The alignment.
/// - Returns: A view.
public func halign(_ align: Alignment) -> AnyView {
wrapModifier(properties: [align]) { storage in
gtk_widget_set_halign(storage.opaquePointer?.cast(), align.cAlign)
}
}
/// Set the vertical alignment.
/// - Parameter align: The alignment.
/// - Returns: A view.
public func valign(_ align: Alignment) -> AnyView {
wrapModifier(properties: [align]) { storage in
gtk_widget_set_valign(storage.opaquePointer?.cast(), align.cAlign)
}
}
/// Set the view's minimal width or height.
/// - Parameters:
/// - minWidth: The minimal width.
/// - minHeight: The minimal height.
/// - Returns: A view.
public func frame(minWidth: Int? = nil, minHeight: Int? = nil) -> AnyView {
wrapModifier(properties: [minWidth, minHeight]) { storage in
gtk_widget_set_size_request(storage.opaquePointer?.cast(), minWidth?.cInt ?? 1, minHeight?.cInt ?? -1)
}
}
/// Add a style class to the view.
/// - Parameters:
/// - style: The style class.
/// - active: Whether the style is currently applied.
/// - Returns: A view.
public func style(_ style: String, active: Bool = true) -> AnyView {
wrapModifier(properties: [style, active]) { storage in
if active {
gtk_widget_add_css_class(storage.opaquePointer?.cast(), style)
} else {
gtk_widget_remove_css_class(storage.opaquePointer?.cast(), style)
}
}
}
/// Make the view insensitive (useful e.g. in overlays).
/// - Parameter insensitive: Whether the view is insensitive.
/// - Returns: A view.
public func insensitive(_ insensitive: Bool = true) -> AnyView {
wrapModifier(properties: [insensitive]) { storage in
gtk_widget_set_sensitive(storage.opaquePointer?.cast(), insensitive ? 0 : 1)
}
}
/// Set the view's visibility.
/// - Parameter visible: Whether the view is visible.
/// - Returns: A view.
public func visible(_ visible: Bool = true) -> AnyView {
wrapModifier(properties: [visible]) { storage in
gtk_widget_set_visible(storage.opaquePointer?.cast(), visible.cBool)
}
}
/// Add a tooltip to the widget.
/// - Parameter tooltip: The tooltip text.
/// - Returns: A view.
public func tooltip(_ tooltip: String) -> AnyView {
wrapModifier(properties: [tooltip]) { storage in
gtk_widget_set_tooltip_markup(storage.opaquePointer?.cast(), tooltip)
}
}
}

View File

@ -15,11 +15,8 @@ public struct Form: SimpleView {
/// The view's body.
public var view: Body {
ModifierWrapper(
content: List([Int](content.indices), selection: nil) { index in content[index] },
style: "boxed-list",
styleActive: true
)
List([Int](content.indices), selection: nil) { index in content[index] }
.style("boxed-list")
}
/// Initialize a `Form`.

View File

@ -17,11 +17,7 @@ public struct HStack: SimpleView {
/// The view's body.
public var view: Body {
ModifierWrapper(
content: VStack(horizontal: true, spacing: spacing, content: content),
style: "linked",
styleActive: linked
)
VStack(horizontal: true, spacing: spacing, content: content)
}
/// Initialize a `HStack`.

View File

@ -1,190 +0,0 @@
//
// ModifierWrapper.swift
// Adwaita
//
// Created by david-swift on 04.09.24.
//
import CAdw
/// A wrapper for view modifiers for any view.
struct ModifierWrapper: AdwaitaWidget {
/// The view.
var content: AnyView
/// The padding.
var padding: Int?
/// The padding edges.
var edges: Set<Edge>?
/// Whether to expand horizontally.
var hexpand: Bool?
/// Whether to expand vertically.
var vexpand: Bool?
/// The horizontal alignment.
var halign: Alignment?
/// The vertical alignment.
var valign: Alignment?
/// The minimum width.
var minWidth: Int?
/// The minimum height.
var minHeight: Int?
/// The style class.
var style: String?
/// Whether the style is active.
var styleActive: Bool?
/// Whether the view is insensitive.
var insensitive: Bool?
/// Whether the view is visible.
var visible: Bool?
/// The tooltip.
var tooltip: String?
/// Initialize a modifier wrapper.
/// - Parameters:
/// - content: The view.
/// - padding: The padding.
/// - edges: The padding edges.
/// - hexpand: Whether to expand horizontally.
/// - vexpand: Whether to expand vertically.
/// - halign: The horizontal alignment.
/// - valign: The vertical alignment.
/// - minWidth: The minimum width.
/// - minHeight: The minimum height.
/// - style: The style class.
/// - styleActive: Whether the style is active.
/// - insensitive: Whether the view is insensitive.
/// - visible: Whether the view is visible.
/// - tooltip: The tooltip.
init(
content: AnyView,
padding: Int? = nil,
edges: Set<Edge>? = nil,
hexpand: Bool? = nil,
vexpand: Bool? = nil,
halign: Alignment? = nil,
valign: Alignment? = nil,
minWidth: Int? = nil,
minHeight: Int? = nil,
style: String? = nil,
styleActive: Bool? = nil,
insensitive: Bool? = nil,
visible: Bool? = nil,
tooltip: String? = nil
) {
self.content = content
self.padding = padding
self.edges = edges
self.hexpand = hexpand
self.vexpand = vexpand
self.halign = halign
self.valign = valign
self.minWidth = minWidth
self.minHeight = minHeight
self.style = style
self.styleActive = styleActive
self.insensitive = insensitive
self.visible = visible
self.tooltip = tooltip
}
/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The view render data type.
/// - Returns: The view storage.
func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
let content = content.storage(data: data, type: type)
return .init(content.pointer, content: [.mainContent: [content]])
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
if let storage = storage.content[.mainContent]?.first {
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
guard updateProperties else {
return
}
update1(storage, data: data, updateProperties: updateProperties, type: type)
update2(storage, data: data, updateProperties: updateProperties, type: type)
storage.previousState = self
}
/// Update part 1 of the properties.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
func update1<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
let previousState = storage.previousState as? Self
if let padding, let edges, previousState?.padding != padding || previousState?.edges != edges {
if edges.contains(.leading) { gtk_widget_set_margin_start(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.trailing) { gtk_widget_set_margin_end(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.top) { gtk_widget_set_margin_top(storage.opaquePointer?.cast(), padding.cInt) }
if edges.contains(.bottom) { gtk_widget_set_margin_bottom(storage.opaquePointer?.cast(), padding.cInt) }
}
if let hexpand, previousState?.hexpand != hexpand {
gtk_widget_set_hexpand(storage.opaquePointer?.cast(), hexpand.cBool)
}
if let vexpand, previousState?.vexpand != vexpand {
gtk_widget_set_vexpand(storage.opaquePointer?.cast(), vexpand.cBool)
}
if let halign, previousState?.halign != halign {
gtk_widget_set_halign(storage.opaquePointer?.cast(), halign.cAlign)
}
if let valign, previousState?.valign != valign {
gtk_widget_set_valign(storage.opaquePointer?.cast(), valign.cAlign)
}
}
/// Update part 2 of the properties.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
func update2<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
let previousState = storage.previousState as? Self
if minWidth != previousState?.minWidth || minHeight != previousState?.minHeight {
gtk_widget_set_size_request(storage.opaquePointer?.cast(), minWidth?.cInt ?? 1, minHeight?.cInt ?? -1)
}
if let style, let styleActive, previousState?.styleActive != styleActive {
if styleActive {
gtk_widget_add_css_class(storage.opaquePointer?.cast(), style)
} else {
gtk_widget_remove_css_class(storage.opaquePointer?.cast(), style)
}
}
if let insensitive, previousState?.insensitive != insensitive {
gtk_widget_set_sensitive(storage.opaquePointer?.cast(), insensitive ? 0 : 1)
}
if let visible, previousState?.visible != visible {
gtk_widget_set_visible(storage.opaquePointer?.cast(), visible.cBool)
}
if let tooltip, previousState?.tooltip != tooltip {
gtk_widget_set_tooltip_markup(storage.opaquePointer?.cast(), tooltip)
}
}
}

View File

@ -28,7 +28,7 @@ struct FlowBoxDemo: View {
selectedItem = items[safe: index]?.id ?? items[safe: index ?? 0 - 1]?.id ?? items.first?.id ?? ""
}
}
.linked()
.modifyContent(VStack.self) { $0.linked() }
.padding()
.halign(.center)
if !items.isEmpty {

View File

@ -28,7 +28,7 @@ struct ListDemo: View {
selectedItem = items[safe: index]?.id ?? items[safe: index ?? 0 - 1]?.id ?? items.first?.id ?? ""
}
}
.linked()
.modifyContent(VStack.self) { $0.linked() }
.padding()
.halign(.center)
if !items.isEmpty {