111 lines
4.2 KiB
Swift
111 lines
4.2 KiB
Swift
import Gtk
|
|
|
|
/// Tracks static children and independently mutable dynamic regions in one host.
|
|
///
|
|
/// A ledger converts a region-local position into the host's absolute position and
|
|
/// supplies the preceding sibling required by sibling-based widget APIs.
|
|
@_spi(Portico) @MainActor public final class ChildLedger {
|
|
private let host: any DynamicChildHost
|
|
fileprivate var regions: [[Gtk.Widget]] = []
|
|
private var staticTail: Int?
|
|
|
|
/// Creates a ledger for a freshly mounted dynamic child host.
|
|
@_spi(Portico) public init(host: any DynamicChildHost) {
|
|
self.host = host
|
|
}
|
|
|
|
/// Records a widget already appended directly to the host.
|
|
@_spi(Portico) public func appendStatic(_ child: Gtk.Widget) {
|
|
if staticTail == nil {
|
|
regions.append([])
|
|
staticTail = regions.count - 1
|
|
}
|
|
regions[staticTail!].append(child)
|
|
}
|
|
|
|
/// Opens an independently mutable region at the current tail.
|
|
@_spi(Portico) public func addRegion() -> ChildRegion {
|
|
regions.append([])
|
|
staticTail = nil
|
|
return ChildRegion(ledger: self, index: regions.count - 1)
|
|
}
|
|
|
|
fileprivate func base(of regionIndex: Int) -> Int {
|
|
regions[..<regionIndex].reduce(0) { $0 + $1.count }
|
|
}
|
|
|
|
fileprivate func widget(before absolute: Int) -> Gtk.Widget? {
|
|
guard absolute > 0 else { return nil }
|
|
var offset = absolute - 1
|
|
for region in regions {
|
|
if offset < region.count {
|
|
return region[offset]
|
|
}
|
|
offset -= region.count
|
|
}
|
|
return nil
|
|
}
|
|
|
|
fileprivate func insert(_ child: Gtk.Widget, at position: Int, in regionIndex: Int) {
|
|
regions[regionIndex].insert(child, at: position)
|
|
let absolute = base(of: regionIndex) + position
|
|
host.insertChild(child, at: absolute, after: widget(before: absolute))
|
|
}
|
|
|
|
fileprivate func remove(at position: Int, in regionIndex: Int) {
|
|
let child = regions[regionIndex].remove(at: position)
|
|
host.removeChild(child)
|
|
}
|
|
|
|
/// Moves a contiguous range to a region-local destination.
|
|
///
|
|
/// Widgets are removed from the host before any are inserted, so index-based
|
|
/// hosts (ListBox, FlowBox, Carousel) that use remove-then-insert see stable
|
|
/// absolute positions for each insertion. The ledger is updated to the final
|
|
/// order before insertion, so the sibling computed from `widget(before:)`
|
|
/// is already in its final host position for sibling-based hosts (Box, WrapBox).
|
|
fileprivate func move(from: Int, count: Int, to: Int, in regionIndex: Int) {
|
|
guard count != 0, from != to else { return }
|
|
let moved = Array(regions[regionIndex][from..<(from + count)])
|
|
for child in moved { host.removeChild(child) }
|
|
regions[regionIndex].removeSubrange(from..<(from + count))
|
|
regions[regionIndex].insert(contentsOf: moved, at: to)
|
|
let base = base(of: regionIndex)
|
|
for (offset, child) in moved.enumerated() {
|
|
let absolute = base + to + offset
|
|
host.insertChild(child, at: absolute, after: widget(before: absolute))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A mutable contiguous region owned by a ``ChildLedger``.
|
|
@_spi(Portico) @MainActor public struct ChildRegion {
|
|
private let ledger: ChildLedger
|
|
private let index: Int
|
|
|
|
/// Creates a region handle for the ledger's current region.
|
|
fileprivate init(ledger: ChildLedger, index: Int) {
|
|
self.ledger = ledger
|
|
self.index = index
|
|
}
|
|
|
|
/// The number of widgets currently held by this region.
|
|
@_spi(Portico) public var count: Int {
|
|
ledger.regions[index].count
|
|
}
|
|
|
|
/// Inserts a widget at a region-local position.
|
|
@_spi(Portico) public func insert(_ child: Gtk.Widget, at position: Int) {
|
|
ledger.insert(child, at: position, in: index)
|
|
}
|
|
|
|
/// Removes the widget at a region-local position.
|
|
@_spi(Portico) public func remove(at position: Int) {
|
|
ledger.remove(at: position, in: index)
|
|
}
|
|
|
|
/// Moves a contiguous range to a region-local destination.
|
|
@_spi(Portico) public func move(from: Int, count: Int, to: Int) {
|
|
ledger.move(from: from, count: count, to: to, in: index)
|
|
}
|
|
}
|