// // AspectContainer.swift // // Copyright 2026 Brendan Szymanski // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-or-later // import Adwaita import CGtkWidgets /// A single‑child container that preserves a fixed aspect ratio. /// /// Unlike GTK's built‑in `GtkAspectFrame`, this container overrides /// `measure()` to report the correct height (`width × ratio`) during /// the layout pass. This makes it work correctly inside `ScrollView`, /// `AdwCarousel`, and other flexible layouts where the parent needs /// to know the child's preferred size. /// /// ```swift /// AspectContainer(aspectRatio: 16.0 / 9.0) { /// .child { /// Picture() /// .data(imageData) /// .halign(.fill) /// .hexpand() /// } /// .maxWidth(800) /// } /// ``` /// /// Ported 1:1 from the Gelata project's `AspectRatioContainer` Rust widget. public struct AspectContainer: Widget { #if exposeGeneratedAppearUpdateFunctions public var updateFunctions: [(ViewStorage, WidgetData, Bool) -> Void] = [] public var appearFunctions: [(ViewStorage, WidgetData) -> Void] = [] #else var updateFunctions: [(ViewStorage, WidgetData, Bool) -> Void] = [] var appearFunctions: [(ViewStorage, WidgetData) -> Void] = [] #endif /// The child widget. var child: Body? /// The desired width‑to‑height ratio. var aspectRatio: Float /// An optional maximum width in pixels (0 = no limit). var maxWidth: Int? /// Create an aspect‑ratio container. /// - Parameter aspectRatio: Desired `width / height` ratio. public init(aspectRatio: Float) { self.aspectRatio = aspectRatio } // MARK: - Widget public func container(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData { let storage = ViewStorage(aspect_container_new(aspectRatio)?.opaque()) for function in appearFunctions { function(storage, data) } if let childStorage = child?.storage(data: data, type: type) { storage.content["child"] = [childStorage] gtk_widget_set_parent( childStorage.opaquePointer?.cast(), storage.opaquePointer?.cast()) } return storage } public func update( _ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type ) where Data: ViewRenderData { storage.modify { widget in if let childStorage = storage.content["child"]?.first { child?.updateStorage( childStorage, data: data, updateProperties: updateProperties, type: type ) } if let maxWidth, updateProperties, (storage.previousState as? Self)?.maxWidth != maxWidth { aspect_container_set_max_width(widget, maxWidth.cInt) } if updateProperties, (storage.previousState as? Self)?.aspectRatio != aspectRatio { aspect_container_set_aspect_ratio(widget, aspectRatio) } } for function in updateFunctions { function(storage, data, updateProperties) } if updateProperties { storage.previousState = self } } } // MARK: - Modifiers extension AspectContainer { /// Set the child widget. public func child(@ViewBuilder _ child: () -> Body) -> Self { modify { $0.child = child() } } /// Set an optional maximum width in pixels. /// When set to a positive value, the container's width is clamped /// before computing the height from the aspect ratio. public func maxWidth(_ maxWidth: Int?) -> Self { modify { $0.maxWidth = maxWidth } } }