From a851dbc1cd16eacbe492b9e494dfb5aefd5da64f Mon Sep 17 00:00:00 2001 From: david-swift Date: Wed, 4 Sep 2024 13:08:45 +0200 Subject: [PATCH] Improve performance of modifiers for any view --- .../Adwaita/View/Modifiers/AnyView++.swift | 129 ---------- Sources/Adwaita/View/Modifiers/AnyView+.swift | 174 +++++++------- .../View/Modifiers/ModifierWrapper.swift | 221 ++++++++++++++++++ 3 files changed, 304 insertions(+), 220 deletions(-) delete mode 100644 Sources/Adwaita/View/Modifiers/AnyView++.swift create mode 100644 Sources/Adwaita/View/Modifiers/ModifierWrapper.swift diff --git a/Sources/Adwaita/View/Modifiers/AnyView++.swift b/Sources/Adwaita/View/Modifiers/AnyView++.swift deleted file mode 100644 index 1923cf2..0000000 --- a/Sources/Adwaita/View/Modifiers/AnyView++.swift +++ /dev/null @@ -1,129 +0,0 @@ -// -// AnyView+.swift -// Adwaita -// -// Created by david-swift on 01.08.24. -// - -import CAdw -import Foundation - -extension AnyView { - - /// Run a function when the view gets an update. - /// - Parameter onUpdate: The function. - /// - Returns: A view. - public func onUpdate(_ onUpdate: @escaping () -> Void) -> AnyView { - inspect { _, _ in onUpdate() } - } - - /// 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 { - inspect { storage, updateProperties in - if updateProperties { - 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 { - inspect { storage, updateProperties in - if updateProperties { - gtk_widget_set_visible(storage.opaquePointer?.cast(), visible.cBool) - } - } - } - - /// Bind to the view's focus. - /// - Parameter focus: Whether the view is focused. - /// - Returns: A view. - public func focused(_ focused: Binding) -> AnyView { - let focus = "focus" - return inspectOnAppear { storage in - let controller = gtk_event_controller_focus_new() - storage.content[focus] = [.init(controller)] - gtk_widget_add_controller(storage.opaquePointer?.cast(), controller) - } - .inspect { storage, _ in - guard let controller = storage.content[focus]?.first else { - return - } - controller.notify(name: "contains-focus", id: "focused") { - let newValue = gtk_event_controller_focus_contains_focus(controller.opaquePointer) != 0 - if focused.wrappedValue != newValue { - focused.wrappedValue = newValue - } - } - if gtk_event_controller_focus_contains_focus(controller.opaquePointer) == 0, focused.wrappedValue { - gtk_widget_grab_focus(storage.opaquePointer?.cast()) - } - } - } - - /// Bind a signal that focuses the view. - /// - Parameter focus: Whether the view is focused. - /// - Returns: A view. - public func focus(_ signal: Signal) -> AnyView { - inspect { storage, _ in - if signal.update { - gtk_widget_grab_focus(storage.opaquePointer?.cast()) - } - } - } - - /// Add a tooltip to the widget. - /// - Parameter tooltip: The tooltip text. - /// - Returns: A view. - public func tooltip(_ tooltip: String) -> AnyView { - inspect { storage, updateProperties in - if updateProperties { - gtk_widget_set_tooltip_markup(storage.opaquePointer?.cast(), tooltip) - } - } - } - - /// Run a function when the view appears for the first time. - /// - Parameter closure: The function. - /// - Returns: A view. - public func onAppear(_ closure: @escaping () -> Void) -> AnyView { - inspectOnAppear { _ in closure() } - } - - /// Run a function when the widget gets clicked. - /// - Parameter handler: The function. - /// - Returns: A view. - public func onClick(handler: @escaping () -> Void) -> AnyView { - inspectOnAppear { storage in - let controller = ViewStorage(gtk_gesture_click_new()) - gtk_widget_add_controller(storage.opaquePointer?.cast(), controller.opaquePointer) - storage.fields["controller"] = controller - let argCount = 3 - controller.connectSignal(name: "released", argCount: argCount, handler: handler) - } - } - - /// Add CSS classes to the app as soon as the view appears. - /// - Parameter getString: Get the CSS. - /// - Returns: A view. - public func css(_ getString: @escaping () -> String) -> AnyView { - inspectOnAppear { _ in - let provider = gtk_css_provider_new() - gtk_css_provider_load_from_string( - provider, - getString() - ) - let display = gdk_display_get_default() - gtk_style_context_add_provider_for_display( - display, - provider?.opaque(), - .init(GTK_STYLE_PROVIDER_PRIORITY_APPLICATION) - ) - } - } - -} diff --git a/Sources/Adwaita/View/Modifiers/AnyView+.swift b/Sources/Adwaita/View/Modifiers/AnyView+.swift index 3f48282..961cd38 100644 --- a/Sources/Adwaita/View/Modifiers/AnyView+.swift +++ b/Sources/Adwaita/View/Modifiers/AnyView+.swift @@ -51,79 +51,6 @@ extension AnyView { .overlay(overlay) } - /// 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 = .all) -> AnyView { - inspect { widget, updateProperties in - if updateProperties { - if edges.contains(.leading) { gtk_widget_set_margin_start(widget.opaquePointer?.cast(), padding.cInt) } - if edges.contains(.trailing) { gtk_widget_set_margin_end(widget.opaquePointer?.cast(), padding.cInt) } - if edges.contains(.top) { gtk_widget_set_margin_top(widget.opaquePointer?.cast(), padding.cInt) } - if edges.contains(.bottom) { gtk_widget_set_margin_bottom(widget.opaquePointer?.cast(), padding.cInt) } - } - } - } - - /// Enable or disable the horizontal expansion. - /// - Parameter enabled: Whether it is enabled or disabled. - /// - Returns: A view. - public func hexpand(_ enabled: Bool = true) -> AnyView { - inspect { storage, updateProperties in - if updateProperties { - 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 { - inspect { storage, updateProperties in - if updateProperties { - 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 { - inspect { storage, updateProperties in - if updateProperties { - 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 { - inspect { storage, updateProperties in - if updateProperties { - 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 { - inspect { storage, updateProperties in - if updateProperties { - gtk_widget_set_size_request(storage.opaquePointer?.cast(), minWidth?.cInt ?? 1, minHeight?.cInt ?? -1) - } - } - } - /// Set the view's transition. /// - Parameter transition: The transition. /// - Returns: A view. @@ -146,24 +73,6 @@ extension AnyView { } } - /// 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 { - inspect { storage, updateProperties in - guard updateProperties else { - return - } - if active { - gtk_widget_add_css_class(storage.opaquePointer?.cast(), style) - } else { - gtk_widget_remove_css_class(storage.opaquePointer?.cast(), style) - } - } - } - /// Make a button or similar widget use accent colors. /// - Parameter active: Whether the style is currently applied. /// - Returns: A view. @@ -362,4 +271,87 @@ extension AnyView { style("frame", active: active) } + /// Run a function when the view gets an update. + /// - Parameter onUpdate: The function. + /// - Returns: A view. + public func onUpdate(_ onUpdate: @escaping () -> Void) -> AnyView { + inspect { _, _ in onUpdate() } + } + + /// Bind to the view's focus. + /// - Parameter focus: Whether the view is focused. + /// - Returns: A view. + public func focused(_ focused: Binding) -> AnyView { + let focus = "focus" + return inspectOnAppear { storage in + let controller = gtk_event_controller_focus_new() + storage.content[focus] = [.init(controller)] + gtk_widget_add_controller(storage.opaquePointer?.cast(), controller) + } + .inspect { storage, _ in + guard let controller = storage.content[focus]?.first else { + return + } + controller.notify(name: "contains-focus", id: "focused") { + let newValue = gtk_event_controller_focus_contains_focus(controller.opaquePointer) != 0 + if focused.wrappedValue != newValue { + focused.wrappedValue = newValue + } + } + if gtk_event_controller_focus_contains_focus(controller.opaquePointer) == 0, focused.wrappedValue { + gtk_widget_grab_focus(storage.opaquePointer?.cast()) + } + } + } + + /// Bind a signal that focuses the view. + /// - Parameter focus: Whether the view is focused. + /// - Returns: A view. + public func focus(_ signal: Signal) -> AnyView { + inspect { storage, _ in + if signal.update { + gtk_widget_grab_focus(storage.opaquePointer?.cast()) + } + } + } + + /// Run a function when the view appears for the first time. + /// - Parameter closure: The function. + /// - Returns: A view. + public func onAppear(_ closure: @escaping () -> Void) -> AnyView { + inspectOnAppear { _ in closure() } + } + + /// Run a function when the widget gets clicked. + /// - Parameter handler: The function. + /// - Returns: A view. + public func onClick(handler: @escaping () -> Void) -> AnyView { + inspectOnAppear { storage in + let controller = ViewStorage(gtk_gesture_click_new()) + gtk_widget_add_controller(storage.opaquePointer?.cast(), controller.opaquePointer) + storage.fields["controller"] = controller + let argCount = 3 + controller.connectSignal(name: "released", argCount: argCount, handler: handler) + } + } + + /// Add CSS classes to the app as soon as the view appears. + /// - Parameter getString: Get the CSS. + /// - Returns: A view. + public func css(_ getString: @escaping () -> String) -> AnyView { + inspectOnAppear { _ in + let provider = gtk_css_provider_new() + gtk_css_provider_load_from_string( + provider, + getString() + ) + let display = gdk_display_get_default() + gtk_style_context_add_provider_for_display( + display, + provider?.opaque(), + .init(GTK_STYLE_PROVIDER_PRIORITY_APPLICATION) + ) + } + } + } diff --git a/Sources/Adwaita/View/Modifiers/ModifierWrapper.swift b/Sources/Adwaita/View/Modifiers/ModifierWrapper.swift new file mode 100644 index 0000000..b9be8d3 --- /dev/null +++ b/Sources/Adwaita/View/Modifiers/ModifierWrapper.swift @@ -0,0 +1,221 @@ +// +// 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? + /// 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? + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The view render data type. + /// - Returns: The view storage. + func container(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData { + let storage = content.storage(data: data, type: type) + update(storage, data: data, updateProperties: true, type: type) + return storage + } + + /// 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( + _ storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: Data.Type + ) where Data: ViewRenderData { + 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( + _ 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( + _ 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) + } + } + +} + +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 = .all) -> AnyView { + ModifierWrapper(content: self, padding: padding, edges: 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) + } + +}