Round commit graph connections

This commit is contained in:
Georg Bauer
2026-07-31 17:44:09 +02:00
parent dba6359d30
commit eadbadd3cb
6 changed files with 144 additions and 49 deletions

View File

@@ -1487,18 +1487,20 @@ public struct CommitRow: Equatable, Hashable {
public var topLanes: [UInt32]
public var bottomLanes: [UInt32]
public var nodeLane: UInt32?
public var connections: [UInt32]
public var topConnections: [UInt32]
public var bottomConnections: [UInt32]
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(sha: String, title: String, detail: String, topLanes: [UInt32], bottomLanes: [UInt32], nodeLane: UInt32?, connections: [UInt32]) {
public init(sha: String, title: String, detail: String, topLanes: [UInt32], bottomLanes: [UInt32], nodeLane: UInt32?, topConnections: [UInt32], bottomConnections: [UInt32]) {
self.sha = sha
self.title = title
self.detail = detail
self.topLanes = topLanes
self.bottomLanes = bottomLanes
self.nodeLane = nodeLane
self.connections = connections
self.topConnections = topConnections
self.bottomConnections = bottomConnections
}
@@ -1523,7 +1525,8 @@ public struct FfiConverterTypeCommitRow: FfiConverterRustBuffer {
topLanes: FfiConverterSequenceUInt32.read(from: &buf),
bottomLanes: FfiConverterSequenceUInt32.read(from: &buf),
nodeLane: FfiConverterOptionUInt32.read(from: &buf),
connections: FfiConverterSequenceUInt32.read(from: &buf)
topConnections: FfiConverterSequenceUInt32.read(from: &buf),
bottomConnections: FfiConverterSequenceUInt32.read(from: &buf)
)
}
@@ -1534,7 +1537,8 @@ public struct FfiConverterTypeCommitRow: FfiConverterRustBuffer {
FfiConverterSequenceUInt32.write(value.topLanes, into: &buf)
FfiConverterSequenceUInt32.write(value.bottomLanes, into: &buf)
FfiConverterOptionUInt32.write(value.nodeLane, into: &buf)
FfiConverterSequenceUInt32.write(value.connections, into: &buf)
FfiConverterSequenceUInt32.write(value.topConnections, into: &buf)
FfiConverterSequenceUInt32.write(value.bottomConnections, into: &buf)
}
}

View File

@@ -1080,6 +1080,9 @@ final class CommitsViewController: RefreshingTableViewController {
}
final class CommitCell: UITableViewCell {
private static let laneOrigin: CGFloat = 12
private static let laneSpacing: CGFloat = 12
private var row: CommitRow?
private var laneCount: UInt32 = 0
@@ -1096,7 +1099,9 @@ final class CommitCell: UITableViewCell {
self.row = row
self.laneCount = laneCount
var content = defaultContentConfiguration()
content.directionalLayoutMargins.leading = laneCount == 0 ? 0 : min(72, CGFloat(laneCount) * 10 + 8)
content.directionalLayoutMargins.leading = laneCount == 0
? 0
: CGFloat(laneCount) * Self.laneSpacing + 10
content.text = row.title
content.secondaryText = row.detail
content.secondaryTextProperties.numberOfLines = 2
@@ -1107,33 +1112,90 @@ final class CommitCell: UITableViewCell {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let row, laneCount > 0, let context = UIGraphicsGetCurrentContext() else { return }
let spacing: CGFloat = 10
let centerY = bounds.midY
let nodeX = row.nodeLane.map(laneX)
context.setLineWidth(3)
context.setLineCap(.round)
context.setLineJoin(.round)
for lane in 0..<laneCount {
let x = 10 + CGFloat(lane) * spacing
let color = laneColor(lane)
context.setStrokeColor(color.cgColor)
context.setLineWidth(3)
if row.topLanes.contains(lane) {
context.move(to: CGPoint(x: x, y: 0))
context.addLine(to: CGPoint(x: x, y: centerY))
context.strokePath()
let start = CGPoint(x: laneX(lane), y: 0)
if row.topConnections.contains(lane), let nodeX {
strokeCurve(
context,
from: start,
to: CGPoint(x: nodeX, y: centerY),
color: laneColor(lane)
)
} else {
strokeLine(
context,
from: start,
to: CGPoint(x: start.x, y: centerY),
color: laneColor(lane)
)
}
}
if row.bottomLanes.contains(lane) {
context.move(to: CGPoint(x: x, y: centerY))
context.addLine(to: CGPoint(x: x, y: bounds.height))
context.strokePath()
}
if row.connections.contains(lane), lane > 0 {
context.move(to: CGPoint(x: x - spacing, y: centerY))
context.addLine(to: CGPoint(x: x, y: centerY))
context.strokePath()
}
if row.nodeLane == lane {
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(x: x - 5, y: centerY - 5, width: 10, height: 10))
let end = CGPoint(x: laneX(lane), y: bounds.height)
if !row.bottomConnections.contains(lane) || row.topLanes.contains(lane) {
strokeLine(
context,
from: CGPoint(x: end.x, y: centerY),
to: end,
color: laneColor(lane)
)
}
if row.bottomConnections.contains(lane), let nodeX {
strokeCurve(
context,
from: CGPoint(x: nodeX, y: centerY),
to: end,
color: laneColor(lane)
)
}
}
}
if let lane = row.nodeLane, let nodeX {
context.setFillColor(laneColor(lane).cgColor)
context.fillEllipse(
in: CGRect(x: nodeX - 5, y: centerY - 5, width: 10, height: 10)
)
}
}
private func laneX(_ lane: UInt32) -> CGFloat {
Self.laneOrigin + CGFloat(lane) * Self.laneSpacing
}
private func strokeLine(
_ context: CGContext,
from start: CGPoint,
to end: CGPoint,
color: UIColor
) {
context.setStrokeColor(color.cgColor)
context.move(to: start)
context.addLine(to: end)
context.strokePath()
}
private func strokeCurve(
_ context: CGContext,
from start: CGPoint,
to end: CGPoint,
color: UIColor
) {
let middleY = (start.y + end.y) / 2
context.setStrokeColor(color.cgColor)
context.move(to: start)
context.addCurve(
to: end,
control1: CGPoint(x: start.x, y: middleY),
control2: CGPoint(x: end.x, y: middleY)
)
context.strokePath()
}
private func laneColor(_ lane: UInt32) -> UIColor {