Add support for text editors
Some checks failed
SwiftLint / SwiftLint (push) Successful in 6s
Deploy Docs / publish (push) Has been cancelled

This commit is contained in:
david-swift 2025-08-09 10:48:04 +02:00
parent a04bd9025d
commit 049b5b54f8
3 changed files with 150 additions and 0 deletions

View File

@ -0,0 +1,105 @@
//
// TextEditor.swift
// Adwaita
//
// Created by david-swift on 09.08.25.
//
import CAdw
/// A text editor widget.
public typealias TextEditor = TextView
/// A text editor widget.
public struct TextView: AdwaitaWidget {
/// The editor's content.
@Binding var text: String
/// The padding between the border and the content.
var padding = 0
/// The edges affected by the padding.
var paddingEdges: Set<Edge> = []
/// 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 {
if paddingEdges.contains(.top) {
gtk_text_view_set_top_margin(storage.opaquePointer?.cast(), padding.cInt)
}
if paddingEdges.contains(.bottom) {
gtk_text_view_set_bottom_margin(storage.opaquePointer?.cast(), padding.cInt)
}
if paddingEdges.contains(.leading) {
gtk_text_view_set_left_margin(storage.opaquePointer?.cast(), padding.cInt)
}
if paddingEdges.contains(.trailing) {
gtk_text_view_set_right_margin(storage.opaquePointer?.cast(), padding.cInt)
}
}
}
/// Get the text view's content.
/// - Parameter buffer: The text view's buffer.
/// - Returns: The content.
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)
gtk_text_buffer_get_end_iter(buffer.opaquePointer?.cast(), endIter)
return .init(
cString: gtk_text_buffer_get_text(buffer.opaquePointer?.cast(), startIter, endIter, true.cBool)
)
}
/// Add padding between the editor's content and border.
/// - Parameters:
/// - padding: The padding's value.
/// - edges: The affected edges.
/// - Returns: The editor.
public func innerPadding(_ padding: Int = 10, edges: Set<Edge> = .all) -> Self {
var newSelf = self
newSelf.padding = padding
newSelf.paddingEdges = edges
return newSelf
}
}

View File

@ -32,6 +32,7 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
case picture
case idle
case fixed
case textEditor
var id: Self {
self
@ -49,6 +50,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
return "Alert Dialog"
case .passwordChecker:
return "Password Checker"
case .textEditor:
return "Text Editor"
default:
return rawValue.capitalized
}
@ -105,6 +108,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
return "Update UI from an asynchronous context"
case .fixed:
return "Place widgets in a coordinate system"
case .textEditor:
return "A simple text editor"
}
}
@ -152,6 +157,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
IdleDemo()
case .fixed:
FixedDemo()
case .textEditor:
TextEditorDemo()
}
}
// swiftlint:enable cyclomatic_complexity

View File

@ -0,0 +1,38 @@
//
// TextEditorDemo.swift
// Adwaita
//
// Created by david-swift on 09.08.25.
//
// swiftlint:disable missing_docs
import Adwaita
import Foundation
struct TextEditorDemo: View {
@State private var text = "Hello, world!"
var view: Body {
VStack(spacing: 20) {
TextEditor(text: $text)
.innerPadding(20)
.frame(minHeight: 60)
.card()
VStack {
Text(text)
.selectable()
.wrap()
.hexpand()
.padding(20)
.halign(.fill)
}
.card()
}
.frame(maxWidth: 500)
}
}
// swiftlint:enable missing_docs