TimelineLayout.h header
#include <ew/app/TimelineLayout.h>
Namespace ew::app
LaidOutCluster struct
struct ew::app::LaidOutCluster
A pile-up of events collapsed into one badge (TL-30), drawn with its count.
Members
QRect ew::app::LaidOutCluster::box
The badge rectangle; the hit target that expands the pile on click/zoom.
int ew::app::LaidOutCluster::count = 0
Number of events collapsed into this badge.
std::vector<ew::core::ContentId> ew::app::LaidOutCluster::members
Expands to EXACTLY these on click/zoom.
LaidOutEvent struct
struct ew::app::LaidOutEvent
One event as placed for drawing.
Members
ew::core::ContentId ew::app::LaidOutEvent::id
Id of the placed event.
QRect ew::app::LaidOutEvent::box
The mark/bar; ALWAYS present and always the hit target.
bool ew::app::LaidOutEvent::hasLabel = false
False when the label was dropped for space – the box still shows, so the event is never invisible, only unlabelled (TL-26).
QRect ew::app::LaidOutEvent::label
Valid only when hasLabel.
bool ew::app::LaidOutEvent::below = false
Which side of the spine this box sits on: false above, true below. Determined by the box's stack row (even rows above, odd below), so a dense moment straddles the axis and each side is half as deep (TL-86).
LayoutEvent struct
struct ew::app::LayoutEvent
One event handed to the layout, already mapped to PIXELS by the caller.
The layout works purely in pixels on purpose: the panel maps time -> x (through TimelineScale, and later through T3.5's piecewise elision), so keeping the engine ignorant of time makes it independent of both and makes its property tests trivial (generate random pixel boxes). Equally, labelWidth is passed IN because libs/app cannot measure a font – the panel measures each title with QFontMetrics, exactly as it feeds TimelineLabelWidths to the ruler.
Members
ew::core::ContentId ew::app::LayoutEvent::id
Id of the source event this box represents.
int ew::app::LayoutEvent::startX = 0
Left pixel of the event's box (caller-mapped).
int ew::app::LayoutEvent::endX = 0
Right pixel; equal to startX for an instant.
int ew::app::LayoutEvent::labelWidth = 0
Measured width of the event's title, in the font on screen.
int ew::app::LayoutEvent::priority = 0
How hard to keep this event's label as room runs out (TL-31): higher wins. The caller folds pinned / selected / conflicted / long-duration into this single rank via labelPriority, so the label-priority policy lives in one tested place and the layout only honours the order.
int ew::app::LayoutEvent::rowSpan = 1
How many stack rows this event's card occupies in the Story layout (R3.1): 1 for a collapsed date -- title pill, more when the pill is EXPANDED to show its detail lines – the card grows outward from the axis and its neighbours stack clear of it. Defaulted so every existing caller keeps single-row pills.
LayoutParams struct
struct ew::app::LayoutParams
The geometry and thresholds the layout places events within. All pixels.
Members
int ew::app::LayoutParams::plotLeft = 0
Left edge of the plot area.
int ew::app::LayoutParams::plotWidth = 0
Width of the plot area.
int ew::app::LayoutParams::laneTop = 0
The lane's vertical bounds. The axis is a SPINE at the lane's mid-line ((laneTop + laneBottom) / 2): events are dealt to alternating sides of it (TL-86), so a dense moment uses both halves of the lane instead of piling down one side, and a label goes on the far side of its box where it cannot collide with the other band. A label that would fall outside [laneTop, laneBottom] is dropped (the box stays), so a stacked lane never bleeds a label into its neighbour. laneBottom must be greater than laneTop.
int ew::app::LayoutParams::laneBottom = 0
Bottom pixel of the lane's vertical bounds; must be greater than laneTop.
int ew::app::LayoutParams::markHeight = 0
Height of an event mark/box.
int ew::app::LayoutParams::minEventWidth = 5
Narrowest a box may be drawn, so an instant is still visible.
int ew::app::LayoutParams::minHitTarget = 5
Narrowest a box may be and still be individually clickable (TL-28).
int ew::app::LayoutParams::spineGap = 2
Clearance between the spine mid-line and the nearest box edge, each side, so the innermost box above the spine and the innermost below it never touch. At least 1 (the default), or two tier-0 boxes on opposite sides would share the spine pixel row and count as overlapping.
int ew::app::LayoutParams::clusterGapPx = 4
How near two boxes must be to belong to the same PILE. Events are grouped into runs whose boxes are within this of each other; a run is the unit that either stacks (if shallow) or collapses into one badge (if too deep – see maxStackRows). It does NOT by itself force a cluster: a wide run of non-overlapping events is one pile of depth 1 and still draws as boxes.
int ew::app::LayoutParams::labelGapPx = 6
Clear space demanded between two label rects.
int ew::app::LayoutParams::labelHeight = 14
Height of a label rect.
int ew::app::LayoutParams::rowPitch = 0
Vertical distance between stacked rows. When events overlap in x they are dealt onto separate rows so each box stays individually visible and clickable (TL-28); a row is rowPitch below the one above it. Zero (the default) keeps every box on the lane's top line – no stacking – which is what a caller that has not opted into stacking gets. For no vertical box overlap, pass a pitch of at least markHeight.
int ew::app::LayoutParams::maxStackRows = 0
The most rows a pile may stack to before it collapses into ONE count badge (TL-30). A run whose greedy stack depth exceeds this becomes a single LaidOutCluster instead of that many boxes, so a day too dense to read shows "12" rather than a tower of unreadable slivers; clicking it (in the panel) zooms in until the pile is shallow enough to stack. Zero (the default) means UNLIMITED – never collapse – so a caller that has not opted into clustering keeps pure stacking (T3.3) and every event stays an individual box.
TimelineLayout struct
struct ew::app::TimelineLayout
The full placement: individually-drawn events, and clusters for the pile-ups.
Members
std::vector<LaidOutEvent> ew::app::TimelineLayout::events
The individually-drawn, hit-testable events.
std::vector<LaidOutCluster> ew::app::TimelineLayout::clusters
The collapsed pile-up badges.
Functions
bool ew::app::eventBoxInViewport(int startX, int endX, int viewportLeft, int viewportRight, int overscanPx)
Whether an event whose drawn box spans [startX, endX] (either order) could put anything – its box, or a label that overhangs it – inside a viewport spanning [viewportLeft, viewportRight]. An overscanPx margin is allowed on each side so a label wider than its box and a cluster straddling the edge are never dropped as the view scrolls.
This is the virtualization gate (TL-72): a project with thousands of events must lay out and measure only what is near the screen, or every pan and zoom stutters. The panel maps each event to pixels (cheap), tests it here, and only then measures its title and feeds it to layoutTimeline – so the O(events) font work and layout shrink to O(events near the view). Culling an off-screen event is safe for hit-testing too: nothing outside the viewport can be under the cursor or inside a marquee drawn within it.
int ew::app::labelPriority(bool pinned, bool selected, bool conflicted, int durationPx)
Folds an event's flags into the single LayoutEvent::priority rank (TL-31).
The order that matters as labels are dropped for room: pinned beats selected beats conflicted beats a long duration beats everything ordinary. Kept a pure free function, separate from the layout, so the policy is tested on its own and the layout stays a mechanical "higher wins".
TimelineLayout ew::app::layoutTimeline(std::vector< LayoutEvent > events, const LayoutParams ¶ms)
Places events within params: clusters the pile-ups, boxes the rest, and assigns each a label only if it fits (TL-26/TL-28/TL-30/TL-31).
Two invariants hold for ANY input (the property tests):
- no two placed label rects intersect (TL-26); a label that cannot fit is dropped, never overlapped, and its box remains;
- every input event appears exactly once – either as a LaidOutEvent with an individually clickable box, or in exactly one cluster's members – so nothing is ever silently invisible (TL-28, i.e. TA-2 restated as an invariant).