diff --git a/Sources/Adwaita/AnyView+.swift b/Sources/Adwaita/AnyView+.swift index 9702baf..c8efdb2 100644 --- a/Sources/Adwaita/AnyView+.swift +++ b/Sources/Adwaita/AnyView+.swift @@ -267,4 +267,36 @@ extension AnyView { .action(button: button, handler: handler) } + /// Add a breakpoint. + /// - Parameters: + /// - maxWidth: The maximum width. + /// - matches: Whether the content view matches the breakpoint. + public func breakpoint(maxWidth: Int, matches: Binding) -> AnyView { + BreakpointBin(condition: .maxWidth(maxWidth), matches: matches) { self } + } + + /// Add a breakpoint. + /// - Parameters: + /// - minWidth: The minimum width. + /// - matches: Whether the content view matches the breakpoint. + public func breakpoint(minWidth: Int, matches: Binding) -> AnyView { + BreakpointBin(condition: .minWidth(minWidth), matches: matches) { self } + } + + /// Add a breakpoint. + /// - Parameters: + /// - maxHeight: The maximum height. + /// - matches: Whether the content view matches the breakpoint. + public func breakpoint(maxHeight: Int, matches: Binding) -> AnyView { + BreakpointBin(condition: .maxHeight(maxHeight), matches: matches) { self } + } + + /// Add a breakpoint. + /// - Parameters: + /// - minHeight: The minimum height. + /// - matches: Whether the content view matches the breakpoint. + public func breakpoint(minHeight: Int, matches: Binding) -> AnyView { + BreakpointBin(condition: .minHeight(minHeight), matches: matches) { self } + } + } diff --git a/Sources/CAdw/shim.h b/Sources/CAdw/shim.h index 4a08ff1..ea9ae7a 100644 --- a/Sources/CAdw/shim.h +++ b/Sources/CAdw/shim.h @@ -52,3 +52,11 @@ gtui_alertdialog_choose (uint64_t dialog, uint64_t data, uint64_t parent) adw_alert_dialog_choose (dialog, parent, NULL, gtui_alertdialog_cb, data); } +static GValue +gtui_initialize_boolean (gboolean boolean) +{ + GValue val = G_VALUE_INIT; + g_value_init(&val, G_TYPE_BOOLEAN); + g_value_set_boolean(&val, boolean); + return val; +} diff --git a/Sources/Core/Model/Extensions/Bool.swift b/Sources/Core/Model/Extensions/Bool.swift index ea35d6f..3c0a73b 100644 --- a/Sources/Core/Model/Extensions/Bool.swift +++ b/Sources/Core/Model/Extensions/Bool.swift @@ -5,6 +5,8 @@ // Created by david-swift on 15.01.24. // +import CAdw + extension Bool { /// Get the gboolean for C. @@ -12,4 +14,11 @@ extension Bool { self ? 1 : 0 } + /// The gboolean as a GValue. + public var gValue: UnsafePointer { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: gtui_initialize_boolean(cBool)) + return .init(pointer) + } + } diff --git a/Sources/Core/View/BreakpointBin.swift b/Sources/Core/View/BreakpointBin.swift new file mode 100644 index 0000000..b090971 --- /dev/null +++ b/Sources/Core/View/BreakpointBin.swift @@ -0,0 +1,115 @@ +// +// BreakpointBin.swift +// Adwaita +// +// Created by david-swift on 21.10.24. +// + +import CAdw +import Meta + +/// Bind a dimension's to observe whether the child view matches a condition. +public struct BreakpointBin: AdwaitaWidget { + + /// The child view. + @ViewProperty( + set: { bin, view in + var width: Int32 = 0 + var height: Int32 = 0 + gtk_widget_measure(view.cast(), GTK_ORIENTATION_VERTICAL, -1, &height, nil, nil, nil) + gtk_widget_measure(view.cast(), GTK_ORIENTATION_HORIZONTAL, -1, &width, nil, nil, nil) + gtk_widget_set_size_request(bin.cast(), width, height) + adw_breakpoint_bin_set_child(bin.cast(), view.cast()) + }, + pointer: OpaquePointer.self, + subview: OpaquePointer.self, + context: AdwaitaMainView.self + ) + var content + + /// The condition. + @Property( + set: { bin, condition, storage in + let condition = adw_breakpoint_condition_parse(condition.condition) + if let breakpoint = storage.fields["breakpoint"] as? OpaquePointer { + g_object_unref(adw_breakpoint_get_condition(breakpoint)?.cast()) + adw_breakpoint_set_condition(breakpoint, condition) + } else { + let breakpoint = adw_breakpoint_new(condition) + adw_breakpoint_bin_add_breakpoint(bin.cast(), breakpoint) + storage.fields["breakpoint"] = breakpoint + if let bin = storage.content["_content"]?.first?.content["append"]?.first { + adw_breakpoint_add_setter( + breakpoint, + bin.opaquePointer?.cast(), + "visible", + false.gValue + ) + } + } + }, + pointer: OpaquePointer.self + ) + var condition: BreakpointCondition = .maxWidth(0) + + /// Whether the child view does not match the condition. + @BindingProperty( + observe: { bin, matches, storage in + storage.notify(name: "current-breakpoint") { + matches.wrappedValue = adw_breakpoint_bin_get_current_breakpoint(bin.cast()) != nil + } + }, + set: { _, _, _ in }, + pointer: OpaquePointer.self + ) + var matches: Binding = .constant(false) + + /// Initialize a breakpoint bin. + /// - Parameters: + /// - condition: The condition. + /// - matches: Whether the content matches the condition. + /// - content: The content. + public init( + condition: BreakpointCondition, + matches: Binding, + @ViewBuilder content: () -> Body + ) { + self.condition = condition + self.matches = matches + self.content = content() + } + + /// Initialize the widget. + public func initializeWidget() -> Any { + adw_breakpoint_bin_new()?.opaque() as Any + } + +} + +/// A breakpoint condition. +public enum BreakpointCondition: Equatable { + + /// Define a maximum width. + case maxWidth(_ width: Int) + /// Define a maximum height. + case maxHeight(_ height: Int) + /// Define a minimum width. + case minWidth(_ width: Int) + /// Define a minimum height. + case minHeight(_ height: Int) + + /// The condition to parse. + var condition: String { + switch self { + case let .maxWidth(width): + "max-width: \(width)sp" + case let .maxHeight(height): + "max-height: \(height)sp" + case let .minWidth(width): + "min-width: \(width)sp" + case let .minHeight(height): + "min-height: \(height)sp" + } + } + +} diff --git a/Sources/Demo/Demo.swift b/Sources/Demo/Demo.swift index d1f6bc9..2e5af77 100644 --- a/Sources/Demo/Demo.swift +++ b/Sources/Demo/Demo.swift @@ -43,7 +43,6 @@ struct Demo: App { } .closeShortcut() .defaultSize(width: 600, height: 400) - .resizable(false) .title("View Switcher Demo") Window(id: "form-demo", open: 0) { _ in FormDemo.WindowContent() diff --git a/Sources/Demo/ViewSwitcherDemo.swift b/Sources/Demo/ViewSwitcherDemo.swift index c72c5ee..38b60a3 100644 --- a/Sources/Demo/ViewSwitcherDemo.swift +++ b/Sources/Demo/ViewSwitcherDemo.swift @@ -8,6 +8,7 @@ // swiftlint:disable missing_docs import Adwaita +import CAdw struct ViewSwitcherDemo: View { @@ -33,10 +34,6 @@ struct ViewSwitcherDemo: View { VStack { Text(selection.title) .padding() - Button(bottom ? "Show Top Bar" : "Show Bottom Bar") { - bottom.toggle() - } - .halign(.center) } .valign(.center) .topToolbar { @@ -44,15 +41,16 @@ struct ViewSwitcherDemo: View { HeaderBar .empty() } else { - toolbar + toolbar(bottom: false) } } .bottomToolbar(visible: bottom) { - toolbar + toolbar(bottom: true) } + .breakpoint(maxWidth: 600, matches: $bottom) } - var toolbar: AnyView { + func toolbar(bottom: Bool) -> AnyView { HeaderBar(titleButtons: !bottom) { } end: { } .headerBarTitle { ViewSwitcher(selectedElement: $selection)