Skip to content

Commit edd0d84

Browse files
committed
Create basic Table view
1 parent c7d219f commit edd0d84

File tree

8 files changed

+252
-6
lines changed

8 files changed

+252
-6
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import GtkBackend
2+
import SwiftCrossUI
3+
4+
struct Person {
5+
var name: String
6+
var age: Int
7+
var phone: String
8+
var email: String
9+
var occupation: String
10+
}
11+
12+
class SpreadSheetState: Observable {
13+
@Observed
14+
var data = [
15+
Person(
16+
name: "Alice", age: 99, phone: "(+61)1234123412",
17+
email: "alice@example.com", occupation: "developer"
18+
),
19+
Person(
20+
name: "Bob", age: 99, phone: "(+61)1234123412",
21+
email: "bob@example.com", occupation: "adversary"
22+
),
23+
]
24+
}
25+
26+
@main
27+
struct SpreadSheetApp: App {
28+
typealias Backend = GtkBackend
29+
30+
let identifier = "dev.stackotter.SpreadSheetApp"
31+
32+
let state = SpreadSheetState()
33+
34+
let windowProperties = WindowProperties(title: "SpreadSheetApp", resizable: true)
35+
36+
var body: some ViewContent {
37+
Table(state.data) {
38+
TableColumn("Name", value: \Person.name)
39+
TableColumn("Age", value: \Person.age)
40+
TableColumn("Phone", value: \Person.phone)
41+
TableColumn("Email", value: \Person.email)
42+
TableColumn("Occupation", value: \Person.occupation)
43+
}
44+
.padding(10)
45+
}
46+
}

Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ let package = Package(
224224
dependencies: exampleDependencies,
225225
path: "Examples/Split"
226226
),
227+
.executableTarget(
228+
name: "SpreadsheetExample",
229+
dependencies: exampleDependencies,
230+
path: "Examples/Spreadsheet"
231+
),
227232

228233
.testTarget(
229234
name: "SwiftCrossUITests",

Sources/Gtk/Widgets/Grid.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import CGtk
1515
public class Grid: Widget, Orientable {
1616
var widgets: [Widget] = []
1717

18-
override init() {
18+
public override init() {
1919
super.init()
2020

2121
widgetPointer = gtk_grid_new()

Sources/GtkBackend/GtkBackend.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,74 @@ public struct GtkBackend: AppBackend {
451451
) {
452452
(container as! ModifierBox).css.set(properties: [.foregroundColor(color.gtkColor)])
453453
}
454+
455+
private class Tables {
456+
var tableSizes: [ObjectIdentifier: (rows: Int, columns: Int)] = [:]
457+
}
458+
459+
private let tables = Tables()
460+
461+
public func createTable(rows: Int, columns: Int) -> Widget {
462+
let widget = Grid()
463+
464+
for i in 0..<rows {
465+
widget.insertRow(position: i)
466+
}
467+
468+
for i in 0..<columns {
469+
widget.insertRow(position: i)
470+
}
471+
472+
tables.tableSizes[ObjectIdentifier(widget)] = (rows: rows, columns: columns)
473+
474+
widget.columnSpacing = 10
475+
widget.rowSpacing = 10
476+
477+
return widget
478+
}
479+
480+
public func setRowCount(ofTable table: Widget, to rows: Int) {
481+
let table = table as! Grid
482+
483+
let rowDifference = rows - tables.tableSizes[ObjectIdentifier(table)]!.rows
484+
tables.tableSizes[ObjectIdentifier(table)]!.rows = rows
485+
if rowDifference < 0 {
486+
for _ in 0..<(-rowDifference) {
487+
table.removeRow(position: 0)
488+
}
489+
} else if rowDifference > 0 {
490+
for _ in 0..<rowDifference {
491+
table.insertRow(position: 0)
492+
}
493+
}
494+
495+
}
496+
497+
public func setColumnCount(ofTable table: Widget, to columns: Int) {
498+
let table = table as! Grid
499+
500+
let columnDifference = columns - tables.tableSizes[ObjectIdentifier(table)]!.columns
501+
tables.tableSizes[ObjectIdentifier(table)]!.columns = columns
502+
if columnDifference < 0 {
503+
for _ in 0..<(-columnDifference) {
504+
table.removeColumn(position: 0)
505+
}
506+
} else if columnDifference > 0 {
507+
for _ in 0..<columnDifference {
508+
table.insertColumn(position: 0)
509+
}
510+
}
511+
512+
}
513+
514+
public func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) {
515+
let table = table as! Grid
516+
table.attach(
517+
child: widget,
518+
left: position.column,
519+
top: position.row,
520+
width: 1,
521+
height: 1
522+
)
523+
}
454524
}

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,32 @@ public protocol AppBackend {
111111

112112
func createForegroundColorContainer(for child: Widget, color: Color) -> Widget
113113
func setForegroundColor(ofForegroundColorContainer container: Widget, to color: Color)
114+
115+
func createTable(rows: Int, columns: Int) -> Widget
116+
func setRowCount(ofTable table: Widget, to rows: Int)
117+
func setColumnCount(ofTable table: Widget, to columns: Int)
118+
func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget)
114119
}
115120

116121
public enum InheritedOrientation {
117122
case vertical
118123
case horizontal
119124
}
120125

126+
/// The position of a cell in a table (with row and column numbers starting from 0).
127+
public struct CellPosition {
128+
/// The row number starting from 0.
129+
public var row: Int
130+
/// The column number starting from 0.
131+
public var column: Int
132+
133+
/// Creates a cell position from a row and column number (starting from 0).
134+
public init(_ row: Int, _ column: Int) {
135+
self.row = row
136+
self.column = column
137+
}
138+
}
139+
121140
extension AppBackend {
122141
public func addChildren(_ children: [Widget], toVStack container: Widget) {
123142
for child in children {
@@ -156,12 +175,12 @@ extension AppBackend {
156175
}
157176
}
158177

159-
private func todo(_ message: String) -> Never {
160-
print(message)
161-
Foundation.exit(1)
162-
}
163-
164178
extension AppBackend {
179+
private func todo(_ message: String) -> Never {
180+
print("\(type(of: self)): message")
181+
Foundation.exit(1)
182+
}
183+
165184
public func show(_ widget: Widget) {
166185
todo("show not implemented")
167186
}
@@ -373,4 +392,17 @@ extension AppBackend {
373392
public func setForegroundColor(ofForegroundColorContainer container: Widget, to color: Color) {
374393
todo("setForegroundColor not implemented")
375394
}
395+
396+
public func createTable(rows: Int, columns: Int) -> Widget {
397+
todo("createTable not implemented")
398+
}
399+
public func setRowCount(ofTable table: Widget, to rows: Int) {
400+
todo("setRowCount not implemented")
401+
}
402+
public func setColumnCount(ofTable table: Widget, to columns: Int) {
403+
todo("setColumnCount not implemented")
404+
}
405+
public func setCell(at position: CellPosition, inTable table: Widget, to widget: Widget) {
406+
todo("setCell not implemented")
407+
}
376408
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@resultBuilder
2+
public struct TableBuilder<Row> {
3+
public static func buildBlock() -> [TableColumn<Row>] {
4+
[]
5+
}
6+
7+
public static func buildBlock(_ components: TableColumn<Row>...) -> [TableColumn<Row>] {
8+
components
9+
}
10+
11+
public static func buildArray(_ components: [TableColumn<Row>]) -> [TableColumn<Row>] {
12+
components
13+
}
14+
15+
public static func buildOptional(_ component: TableColumn<Row>?) -> [TableColumn<Row>] {
16+
if let component = component {
17+
[component]
18+
} else {
19+
[]
20+
}
21+
}
22+
23+
public static func buildEither(first component: TableColumn<Row>) -> [TableColumn<Row>] {
24+
[component]
25+
}
26+
27+
public static func buildEither(second component: TableColumn<Row>) -> [TableColumn<Row>] {
28+
[component]
29+
}
30+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// A table view.
2+
public struct Table<Row>: View {
3+
public var body = EmptyViewContent()
4+
5+
private var rows: [Row]
6+
private var columns: [TableColumn<Row>]
7+
8+
/// Creates a new text view with the given content.
9+
public init(_ rows: [Row], @TableBuilder<Row> _ columns: () -> [TableColumn<Row>]) {
10+
self.rows = rows
11+
self.columns = columns()
12+
}
13+
14+
public func asWidget<Backend: AppBackend>(
15+
_ children: EmptyViewContent.Children,
16+
backend: Backend
17+
) -> Backend.Widget {
18+
return backend.createTable(rows: rows.count, columns: columns.count)
19+
}
20+
21+
public func update<Backend: AppBackend>(
22+
_ widget: Backend.Widget,
23+
children: EmptyViewContent.Children,
24+
backend: Backend
25+
) {
26+
backend.setRowCount(ofTable: widget, to: rows.count)
27+
backend.setColumnCount(ofTable: widget, to: columns.count)
28+
for i in 0..<rows.count {
29+
let row = rows[i]
30+
for j in 0..<columns.count {
31+
backend.setCell(
32+
at: CellPosition(i, j),
33+
inTable: widget,
34+
to: backend.createTextView(
35+
content: columns[j].value(row),
36+
shouldWrap: false
37+
)
38+
)
39+
}
40+
}
41+
}
42+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
public struct TableColumn<Row> {
2+
public var title: String
3+
public var value: (Row) -> String
4+
5+
public init(_ title: String, value keyPath: KeyPath<Row, String>) {
6+
self.title = title
7+
self.value = { row in
8+
row[keyPath: keyPath]
9+
}
10+
}
11+
12+
public init<Value: CustomStringConvertible>(
13+
_ title: String,
14+
value keyPath: KeyPath<Row, Value>
15+
) {
16+
self.title = title
17+
self.value = { row in
18+
row[keyPath: keyPath].description
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)