Add support for text editors
This commit is contained in:
parent
a04bd9025d
commit
049b5b54f8
105
Sources/Core/View/TextEditor.swift
Normal file
105
Sources/Core/View/TextEditor.swift
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -32,6 +32,7 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
|
|||||||
case picture
|
case picture
|
||||||
case idle
|
case idle
|
||||||
case fixed
|
case fixed
|
||||||
|
case textEditor
|
||||||
|
|
||||||
var id: Self {
|
var id: Self {
|
||||||
self
|
self
|
||||||
@ -49,6 +50,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
|
|||||||
return "Alert Dialog"
|
return "Alert Dialog"
|
||||||
case .passwordChecker:
|
case .passwordChecker:
|
||||||
return "Password Checker"
|
return "Password Checker"
|
||||||
|
case .textEditor:
|
||||||
|
return "Text Editor"
|
||||||
default:
|
default:
|
||||||
return rawValue.capitalized
|
return rawValue.capitalized
|
||||||
}
|
}
|
||||||
@ -105,6 +108,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
|
|||||||
return "Update UI from an asynchronous context"
|
return "Update UI from an asynchronous context"
|
||||||
case .fixed:
|
case .fixed:
|
||||||
return "Place widgets in a coordinate system"
|
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()
|
IdleDemo()
|
||||||
case .fixed:
|
case .fixed:
|
||||||
FixedDemo()
|
FixedDemo()
|
||||||
|
case .textEditor:
|
||||||
|
TextEditorDemo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable cyclomatic_complexity
|
// swiftlint:enable cyclomatic_complexity
|
||||||
|
|||||||
38
Sources/Demo/TextEditorDemo.swift
Normal file
38
Sources/Demo/TextEditorDemo.swift
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user