# Calculating Timeline Geometry for UI Rendering in PalmierPro: A Deep Dive into the TimelineGeometry Struct

> Learn how PalmierPro calculates timeline geometry for UI rendering with a stateless TimelineGeometry struct. Achieve deterministic hit-testing and rendering through pure math.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: deep-dive
- Published: 2026-06-24

---

**PalmierPro calculates timeline geometry for UI rendering using a stateless `TimelineGeometry` struct that performs pure mathematical conversions between frame/track indices and screen coordinates, enabling deterministic hit-testing and rendering.**

The PalmierPro video editor renders its non-linear timeline entirely in Swift, requiring precise calculations to map logical timeline data (frames, tracks, clips) onto screen coordinates. According to the palmier-io/palmier-pro source code, the heavy lifting is centralized in the **`TimelineGeometry`** struct located at [`Sources/PalmierPro/Timeline/TimelineGeometry.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Timeline/TimelineGeometry.swift), which separates pure geometry calculations from rendering and input handling.

## The Stateless Architecture of TimelineGeometry

`TimelineGeometry` is deliberately **stateless**, storing only the values required to define the current layout. This design ensures that the same struct can be shared between the drawing code in `TimelineView` and the input controller (`TimelineInputController`), guaranteeing that hit-testing and rendering remain perfectly synchronized during scroll or zoom operations.

The struct initializes with these core properties:

- **`pixelsPerFrame`** – Horizontal scale (pixels per frame) derived from the editor's zoom level
- **`headerWidth`** – Width of the optional left-hand header for track lists and thumbnails
- **`rulerHeight`** – Fixed vertical height of the time-code ruler (defined as `Layout.rulerHeight`)
- **`trackHeights`** – Height of each individual track (typically `Layout.trackHeight`)
- **`bounds`** – The view's clipping rectangle used for scroll calculations

During initialization, `TimelineGeometry` pre-computes a **cumulative Y array** (`cumulativeY`) that stores the top-edge Y-coordinate of each track. This optimization makes every track-to-Y lookup O(1) instead of O(n), critical for performance during rapid mouse movements and redraws.

## Converting Between Frames and Pixels

The primary responsibility of calculating timeline geometry for UI rendering involves bidirectional conversion between logical frame indices and horizontal pixel positions.

**`frameAt(x:)`** converts a horizontal pixel coordinate to a frame index:

```swift
let frame = geometry.frameAt(x: mouseX)  // pixel → frame index

```

**`xForFrame(_:)`** performs the inverse operation, returning the pixel X-coordinate for a given frame:

```swift
let xPos = geometry.xForFrame(frame)       // frame index → pixel

```

These methods serve as the foundation for hit-testing in `TimelineInputController`, determining precisely which frame the cursor points at during scrubbing or clip positioning.

## Mapping Tracks to Y Coordinates

Vertical positioning follows a similar pattern using the pre-computed cumulative array.

**`trackAt(y:)`** determines which track index contains a specific Y-coordinate:

```swift
let trackIdx = geometry.trackAt(y: mouseY)  // pixel → track index

```

**`trackY(at:)`** retrieves the top-edge Y-coordinate for a specific track:

```swift
let yTop = geometry.trackY(at: trackIdx)  // track index → top-edge Y

```

Because the struct maintains the `cumulativeY` array, these lookups execute in constant time regardless of track count, ensuring smooth performance even with complex timelines containing dozens of tracks.

## Calculating Clip Rectangles for Rendering

When `TimelineView` needs to draw clips, it relies on `TimelineGeometry` to calculate exact screen rectangles.

**`clipRect(for:trackIndex:)`** returns the precise rectangle for a clip on a specific track:

```swift
let rect = geometry.clipRect(for: clip, trackIndex: trackIdx)
ClipRenderer.draw(clip,
                  type: clip.mediaType,
                  in: rect,
                  isSelected: isSelected,
                  context: ctx,
                  cache: editor.mediaVisualCache)

```

For more generic positioning, **`clipRect(for:atY:height:)`** accepts explicit Y-coordinates and heights, enabling the rendering of ghost clips and temporary overlays outside standard track layouts.

## Geometry for Drag-and-Drop Operations

Calculating timeline geometry for UI rendering becomes particularly complex during drag-and-drop interactions, where the system must determine valid drop zones and render visual feedback.

**`dropTargetAt(y:)`** analyzes a Y-coordinate and returns a `TrackDropTarget` enum indicating whether the user is hovering over an existing track or a potential new track insertion point:

```swift
let target = geometry.dropTargetAt(y: dragY)
switch target {
case .newTrackAt(let index):
    // Show insertion line at geometry.insertionLineY(for: target)
case .existingTrack(let index):
    // Highlight the existing track
}

```

**`insertionLineY(for:)`** calculates the exact Y-coordinate for drawing the visual insertion line when creating new tracks:

```swift
if let lineY = geometry.insertionLineY(for: dropTarget) {
    ctx.setStrokeColor(NSColor.systemYellow.cgColor)
    ctx.setLineWidth(2)
    ctx.move(to: CGPoint(x: 0, y: Double(lineY)))
    ctx.addLine(to: CGPoint(x: Double(bounds.width), y: Double(lineY)))
    ctx.strokePath()
}

```

**`ghostY(for:height:)`** determines the Y-position for "ghost" clips—semi-transparent representations of clips being dragged to new track drop zones:

```swift
if let ghostY = geometry.ghostY(for: dragTarget) {
    let ghostRect = geometry.clipRect(for: ghostClip,
                                      atY: Double(ghostY),
                                      height: Layout.trackHeight)
    // draw the ghost with reduced opacity
}

```

## UI Constants and Design System Integration

The geometric calculations rely on centralized layout constants defined in [`Sources/PalmierPro/UI/Layout.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/Layout.swift), including `rulerHeight`, `dropZoneHeight`, `insertThreshold`, and `trackHeight`. These values form part of PalmierPro's broader design system, which also encompasses [`AppTheme.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AppTheme.swift) for colors, spacing, and fonts.

Because all measurements derive from these constants, the geometry engine remains consistent with the application's visual design while allowing for easy theming and layout adjustments.

## Summary

- **`TimelineGeometry`** in [`Sources/PalmierPro/Timeline/TimelineGeometry.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Timeline/TimelineGeometry.swift) provides pure, side-effect-free calculations for mapping timeline data to screen coordinates.
- The struct maintains a **cumulative Y array** for O(1) track-to-coordinate lookups, optimizing performance during interaction.
- **Bidirectional conversion methods** (`frameAt(x:)`/`xForFrame(_:)` and `trackAt(y:)`/`trackY(at:)`) synchronize rendering and input handling.
- **Drop-target detection** (`dropTargetAt(y:)`) and associated geometry methods (`insertionLineY`, `ghostY`) enable sophisticated drag-and-drop interactions.
- Layout constants in [`Sources/PalmierPro/UI/Layout.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/Layout.swift) ensure geometric calculations align with the application's design system.

## Frequently Asked Questions

### How does PalmierPro handle coordinate conversion when the user zooms the timeline?

When the zoom level changes, `TimelineView` creates a new `TimelineGeometry` instance with an updated `pixelsPerFrame` value derived from the editor's current zoom state. Because the struct is stateless and recalculated on each frame, all subsequent coordinate conversions automatically reflect the new scale without requiring manual invalidation of cached values.

### Why is TimelineGeometry designed as a struct rather than a class?

`TimelineGeometry` uses a struct to enforce value semantics and immutability. This design guarantees that the geometry used during the rendering phase matches exactly the geometry used for hit-testing, preventing synchronization bugs when the timeline scrolls or zooms between input processing and screen drawing.

### Where does PalmierPro store the height constants for tracks and rulers?

Height constants such as `trackHeight`, `rulerHeight`, and `dropZoneHeight` reside in [`Sources/PalmierPro/UI/Layout.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/Layout.swift). These static values provide the default measurements that `TimelineGeometry` uses when calculating cumulative Y positions and clip rectangles.

### How does the geometry engine support creating new tracks via drag-and-drop?

The `dropTargetAt(y:)` method detects when the user drags a clip to an area between existing tracks, returning a `TrackDropTarget.newTrackAt(index)` case. The view then uses `insertionLineY(for:)` to draw a visual indicator and `ghostY(for:height:)` to position a preview of the clip before the user releases the mouse, providing immediate visual feedback about the new track creation.