Merge pull request 'Add WrapMode to TextView/TextEditor' (#68) from mlm/adwaita-swift:AddWrapMode into main
Some checks failed
SwiftLint / SwiftLint (push) Failing after 21m8s
Deploy Docs / publish (push) Has been cancelled

Reviewed-on: #68
This commit is contained in:
david-swift 2025-08-27 13:36:21 +02:00
commit ebb2455fe1
3 changed files with 138 additions and 47 deletions

View File

@ -0,0 +1,43 @@
//
// WrapMode.swift
// Adwaita
//
// Created by mlm on 24.08.25.
//
import CAdw
/// Wrap modes for `TextView`/`TextEditor`
public enum WrapMode: GtkWrapMode, RawRepresentable {
// swiftlint:disable discouraged_none_name
/// GTK_WRAP_NONE
case none
// swiftlint:enable discouraged_none_name
/// GTK_WRAP_CHAR
case char
/// GTK_WRAP_WORD
case word
/// GTK_WRAP_WORD_CHAR
case wordChar
/// Get the GtkWrapMode.
public var rawValue: GtkWrapMode {
switch self {
case .none:
GTK_WRAP_NONE
case .char:
GTK_WRAP_CHAR
case .word:
GTK_WRAP_WORD
case .wordChar:
GTK_WRAP_WORD_CHAR
}
}
/// Initialize from the GtkWrapMode.
/// - Parameter rawValue: The GtkWrapMode.
public init?(rawValue: GtkWrapMode) {
nil
}
}

View File

@ -0,0 +1,19 @@
//
// GtkWrapMode.swift
// Adwaita
//
// Created by mlm on 24.08.25.
//
import CAdw
/// Add ExpressibleByIntegerLiteral conformance to make GtkWrapMode usable as
/// a RawValue in an enum.
extension GtkWrapMode: @retroactive ExpressibleByIntegerLiteral {
/// Initialize from an integer literal.
public init(integerLiteral value: Int) {
self.init(UInt32(value))
}
}

View File

@ -14,73 +14,75 @@ public typealias TextEditor = TextView
public struct TextView: AdwaitaWidget {
/// The editor's content.
@Binding var text: String
@BindingProperty(
observe: { _, text, storage in
if let buffer = storage.content["buffer"]?.first {
buffer.connectSignal(name: "changed") {
let currentText = Self.getText(buffer: buffer)
if text.wrappedValue != currentText {
text.wrappedValue = currentText
}
}
}
},
set: { _, text, storage in
if let buffer = storage.content["buffer"]?.first, Self.getText(buffer: buffer) != text {
gtk_text_buffer_set_text(buffer.opaquePointer?.cast(), text, -1)
}
},
pointer: Any.self
)
var text: Binding<String> = .constant("")
/// The padding between the border and the content.
var padding = 0
/// The edges affected by the padding.
var paddingEdges: Set<Edge> = []
@Property(
set: { $1.set($0) },
pointer: OpaquePointer.self
)
var padding: InnerPadding?
/// The (word) wrap mode used when rendering the text.
@Property(
set: { gtk_text_view_set_wrap_mode($0.cast(), $1.rawValue) },
pointer: OpaquePointer.self
)
var wrapMode: WrapMode = .none
/// Initialize a text editor.
/// - Parameter text: The editor's content.
public init(text: Binding<String>) {
self._text = text
}
/// Get the editor's view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage {
let buffer = ViewStorage(gtk_text_buffer_new(nil)?.opaque())
let editor = ViewStorage(
gtk_text_view_new_with_buffer(buffer.opaquePointer?.cast())?.opaque(),
content: ["buffer": [buffer]]
)
update(editor, data: data, updateProperties: true, type: type)
return editor
}
/// Update a view storage to the editor.
/// - Parameters:
/// - storage: The view storage.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
if let buffer = storage.content["buffer"]?.first {
buffer.connectSignal(name: "changed") {
let text = getText(buffer: buffer)
if self.text != text {
self.text = text
}
}
if updateProperties {
if getText(buffer: buffer) != self.text {
gtk_text_buffer_set_text(buffer.opaquePointer?.cast(), text, -1)
}
}
}
if updateProperties {
/// The inner padding of a text view.
struct InnerPadding {
/// The padding.
var padding: Int
/// The affected edges.
var paddingEdges: Set<Edge>
/// Set the inner padding on a text view.
/// - Parameter pointer: The text view.
func set(_ pointer: OpaquePointer) {
if paddingEdges.contains(.top) {
gtk_text_view_set_top_margin(storage.opaquePointer?.cast(), padding.cInt)
gtk_text_view_set_top_margin(pointer.cast(), padding.cInt)
}
if paddingEdges.contains(.bottom) {
gtk_text_view_set_bottom_margin(storage.opaquePointer?.cast(), padding.cInt)
gtk_text_view_set_bottom_margin(pointer.cast(), padding.cInt)
}
if paddingEdges.contains(.leading) {
gtk_text_view_set_left_margin(storage.opaquePointer?.cast(), padding.cInt)
gtk_text_view_set_left_margin(pointer.cast(), padding.cInt)
}
if paddingEdges.contains(.trailing) {
gtk_text_view_set_right_margin(storage.opaquePointer?.cast(), padding.cInt)
gtk_text_view_set_right_margin(pointer.cast(), padding.cInt)
}
}
}
/// Get the text view's content.
/// - Parameter buffer: The text view's buffer.
/// - Returns: The content.
func getText(buffer: ViewStorage) -> String {
static func getText(buffer: ViewStorage) -> String {
let startIter: UnsafeMutablePointer<GtkTextIter> = .allocate(capacity: 1)
let endIter: UnsafeMutablePointer<GtkTextIter> = .allocate(capacity: 1)
gtk_text_buffer_get_start_iter(buffer.opaquePointer?.cast(), startIter)
@ -90,6 +92,34 @@ public struct TextView: AdwaitaWidget {
)
}
/// Get the editor's view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
let buffer = ViewStorage(gtk_text_buffer_new(nil)?.opaque())
let editor = ViewStorage(
gtk_text_view_new_with_buffer(buffer.opaquePointer?.cast())?.opaque(),
content: ["buffer": [buffer]]
)
initProperties(editor, data: data, type: type)
update(editor, data: data, updateProperties: true, type: type)
return editor
}
/// Set the wrapMode for the text view.
///
/// Available wrap modes are `none`, `char`, `word`, `wordChar`. Please refer to the
/// corresponding `GtkWrapMode` documentation for the details on how they work.
///
/// - Parameter mode: The `WrapMode` to set.
public func wrapMode(_ mode: WrapMode) -> Self {
var newSelf = self
newSelf.wrapMode = mode
return newSelf
}
/// Add padding between the editor's content and border.
/// - Parameters:
/// - padding: The padding's value.
@ -97,8 +127,7 @@ public struct TextView: AdwaitaWidget {
/// - Returns: The editor.
public func innerPadding(_ padding: Int = 10, edges: Set<Edge> = .all) -> Self {
var newSelf = self
newSelf.padding = padding
newSelf.paddingEdges = edges
newSelf.padding = .init(padding: padding, paddingEdges: edges)
return newSelf
}