From d17306f1951ac14905271722fea5e28be2bb5800 Mon Sep 17 00:00:00 2001 From: david-swift Date: Thu, 18 Sep 2025 21:22:20 +0200 Subject: [PATCH] Add HSplitView and VSplitView Co-authored-by: desbeers nick@desbeers.nl --- .../Core/Model/Enumerations/WrapMode.swift | 24 ++-- Sources/Core/View/SplitView.swift | 131 ++++++++++++++++++ Sources/Demo/Page.swift | 7 + Sources/Demo/SplitViewDemo.swift | 29 ++++ 4 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 Sources/Core/View/SplitView.swift create mode 100644 Sources/Demo/SplitViewDemo.swift diff --git a/Sources/Core/Model/Enumerations/WrapMode.swift b/Sources/Core/Model/Enumerations/WrapMode.swift index 4128589..dd8dff3 100644 --- a/Sources/Core/Model/Enumerations/WrapMode.swift +++ b/Sources/Core/Model/Enumerations/WrapMode.swift @@ -38,17 +38,17 @@ public enum WrapMode: GtkWrapMode, RawRepresentable { /// Initialize from the GtkWrapMode. /// - Parameter rawValue: The GtkWrapMode. public init?(rawValue: GtkWrapMode) { - switch rawValue { - case GTK_WRAP_NONE: - self = .none - case GTK_WRAP_CHAR: - self = .char - case GTK_WRAP_WORD: - self = .word - case GTK_WRAP_WORD_CHAR: - self = .wordChar - default: - return nil - } + switch rawValue { + case GTK_WRAP_NONE: + self = .none + case GTK_WRAP_CHAR: + self = .char + case GTK_WRAP_WORD: + self = .word + case GTK_WRAP_WORD_CHAR: + self = .wordChar + default: + return nil + } } } diff --git a/Sources/Core/View/SplitView.swift b/Sources/Core/View/SplitView.swift new file mode 100644 index 0000000..58e75bf --- /dev/null +++ b/Sources/Core/View/SplitView.swift @@ -0,0 +1,131 @@ +// +// SplitView.swift +// Adwaita +// +// Created by david-swift on 19.09.2025. +// + +import CAdw +import Foundation + +/// A split view. +struct SplitView: AdwaitaWidget { + + /// The start widget. + @ViewProperty( + set: { widget, view in gtk_paned_set_start_child(.init(widget), view.cast()) }, + pointer: OpaquePointer.self, + subview: OpaquePointer.self, + context: AdwaitaMainView.self + ) + var start + /// The end widget. + @ViewProperty( + set: { widget, view in gtk_paned_set_end_child(.init(widget), view.cast()) }, + pointer: OpaquePointer.self, + subview: OpaquePointer.self, + context: AdwaitaMainView.self + ) + var end + /// Position of the splitter + @BindingProperty( + observe: { _, binding, storage in + storage.notify(name: "position") { + binding.wrappedValue = Int(gtk_paned_get_position(storage.opaquePointer)) + } + }, + set: { widget, value, _ in gtk_paned_set_position(widget, Int32(value)) }, + pointer: OpaquePointer.self + ) + var splitter: Binding = .constant(0) + /// Whether the split view is vertical. + var vertical: Bool + + /// Initialize a split view. + /// - Parameters: + /// - splitter: The position of the splitter. + /// - vertical: Whether to make the splitter vertical. + /// - start: The start widget. + /// - end: The end widget. + init( + splitter: Binding, + vertical: Bool, + start: Body, + end: Body + ) { + self.vertical = vertical + self.splitter = splitter + self.start = start + self.end = end + } + + /// Initialize the widget. + func initializeWidget() -> Any { + gtk_paned_new(vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL).opaque() as Any + } + +} + +/// A horizontal split view. +public struct HSplitView: SimpleView { + + /// The splitter. + @Binding var splitter: Int + /// The start widget. + var start: Body + /// The end widget. + var end: Body + + /// The view. + public var view: Body { + SplitView(splitter: $splitter, vertical: false, start: start, end: end) + } + + /// Initialize a horizontal split view. + /// - Parameters: + /// - splitter: The position of the splitter. + /// - start: The start widget. + /// - end: The end widget. + public init( + splitter: Binding, + @ViewBuilder start: () -> Body, + @ViewBuilder end: () -> Body + ) { + self._splitter = splitter + self.start = start() + self.end = end() + } + +} + +/// A vertical split view. +public struct VSplitView: SimpleView { + + /// The splitter. + @Binding var splitter: Int + /// The start widget. + var start: Body + /// The end widget. + var end: Body + + /// The view. + public var view: Body { + SplitView(splitter: $splitter, vertical: true, start: start, end: end) + } + + /// Initialize a vertical split view. + /// - Parameters: + /// - splitter: The position of the splitter. + /// - start: The start widget. + /// - end: The end widget. + public init( + splitter: Binding, + @ViewBuilder start: () -> Body, + @ViewBuilder end: () -> Body + ) { + self._splitter = splitter + self.start = start() + self.end = end() + } + +} diff --git a/Sources/Demo/Page.swift b/Sources/Demo/Page.swift index 1654bd8..560da1f 100644 --- a/Sources/Demo/Page.swift +++ b/Sources/Demo/Page.swift @@ -33,6 +33,7 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible case idle case fixed case textEditor + case splitView var id: Self { self @@ -52,6 +53,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible return "Password Checker" case .textEditor: return "Text Editor" + case .splitView: + return "Split View" default: return rawValue.capitalized } @@ -110,6 +113,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible return "Place widgets in a coordinate system" case .textEditor: return "A simple text editor" + case .splitView: + return "A split view whose panes are ajustable in size" } } @@ -159,6 +164,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible FixedDemo() case .textEditor: TextEditorDemo() + case .splitView: + SplitViewDemo() } } // swiftlint:enable cyclomatic_complexity diff --git a/Sources/Demo/SplitViewDemo.swift b/Sources/Demo/SplitViewDemo.swift new file mode 100644 index 0000000..72c860e --- /dev/null +++ b/Sources/Demo/SplitViewDemo.swift @@ -0,0 +1,29 @@ +// +// SplitViewDemo.swift +// Adwaita +// +// Created by david-swift on 16.09.25. +// + +// swiftlint:disable missing_docs + +import Adwaita + +struct SplitViewDemo: View { + + @State private var splitter = 200 + + var view: Body { + HSplitView(splitter: $splitter) { + Text("\(splitter) Pixels") + } end: { + Text("View 2") + } + .frame(minHeight: 200) + .card() + .padding() + } + +} + +// swiftlint:enable missing_docs