TimelineTicks.h header
#include <ew/app/TimelineTicks.h>
Namespace ew::app
RulerLabel struct
struct ew::app::RulerLabel
One label competing for horizontal room in a band: the pixel x of its LEFT edge and its width (measured by the caller in its own font). Used to decide which labels a band may draw without any two overprinting.
Members
int ew::app::RulerLabel::left = 0
The pixel x of the label's left edge.
int ew::app::RulerLabel::width = 0
The label's width in pixels (measured in the caller's font).
TimelineLabelWidths struct
struct ew::app::TimelineLabelWidths
How much horizontal room one gradation needs at each unit: the width of the widest label that unit can emit, plus the clear space that must separate it from its neighbour.
Per-unit, not one number. A single flat spacing has to be the widest label the ruler could EVER emit – a day number followed by the longest month name – and reserving that much at Year zoom, where the label is "1250 SA" and a third as wide, thins the ruler to two labels on a screen that would hold ten. TL-15 forbids both failures in one breath: gradations "must never crowd into an unreadable smear, and must never thin out to two labels on a wide screen". One number cannot satisfy both halves, because the two halves are about different units.
A value of zero or less is treated as one pixel, so a caller that forgets to fill a field gets a dense ruler rather than a division by zero.
Members
int ew::app::TimelineLabelWidths::minute = 1
The minor band, per unit: "06:15", "25", "Harvestmoon", "1250 SA".
int ew::app::TimelineLabelWidths::hour = 1
Room to reserve for an Hour minor-band label.
int ew::app::TimelineLabelWidths::day = 1
Room to reserve for a Day minor-band label.
int ew::app::TimelineLabelWidths::month = 1
Room to reserve for a Month minor-band label.
int ew::app::TimelineLabelWidths::year = 1
Room to reserve for a Year minor-band label.
int ew::app::TimelineLabelWidths::minuteContext = 1
The context band drawn above each unit – keyed by the minor unit, not by the band's own, because the same Month band reads "Harvestmoon" when it is the minor band and "Harvestmoon 1250 SA" when it is the context above days. One width per band-unit would be measuring the wrong string.
Nor may these collapse to a single "widest context" number. Traced: at the widest Month view (~2663 minutes per pixel) a Year boundary is 518,400 minutes away, so the real Month context ("1250 SA", 74px) fits at 197,062 – but a flat 250px context asks 665,750, rejects Month/6, and drops the ruler to Year/1. That is exactly the flat-spacing failure TL-15 describes, re-created one band up.
int ew::app::TimelineLabelWidths::hourContext = 1
Room to reserve for the context band above an Hour ruler.
int ew::app::TimelineLabelWidths::dayContext = 1
Room to reserve for the context band above a Day ruler.
int ew::app::TimelineLabelWidths::monthContext = 1
Room to reserve for the context band above a Month ruler.
TimelineRuler struct
struct ew::app::TimelineRuler
A ruler: two bands of gradations over the visible span.
Members
TimelineTickUnit ew::app::TimelineRuler::unit =
The unit and step the minor band is using (e.g. Year/50, Month/3, Hour/6).
qint64 ew::app::TimelineRuler::step = 1
The number of unit units between minor gradations.
std::vector<TimelineTick> ew::app::TimelineRuler::minor
The fine band: evenly spaced, human-round, and never closer than the caller's minimum spacing.
std::vector<TimelineTick> ew::app::TimelineRuler::major
The coarse band above it, carrying what the minor band leaves out – "which month and year are these days in?". TL-16: a major band over a minor one is the difference between knowing where you are and reading numbers.
The two bands divide the labour, which is why a Day gradation reads a bare "25" and this band reads "Harvestmoon 1250 SA": precision below, context above, neither repeating the other.
Unless a real boundary falls right at the left edge, the first entry sits at the view's start rather than on a boundary: it names the context you are already inside, so the band is never empty merely because the view happens to sit within one month. Every entry is placed by its instant, so a view needs no special case to draw it.
**Empty when unit is Year**, deliberately, and a view must not treat that as a bug: a year label is already self-describing ("1250 SA" says exactly where you are, "Harvestmoon" does not), and years are the top of the ladder, so any coarser band would be either redundant with the minor one or entirely off-screen.
TimelineTick struct
struct ew::app::TimelineTick
One gradation on a timeline's ruler: where it falls, and what to write there.
Members
qint64 ew::app::TimelineTick::instant = 0
The instant, in minutes since the story epoch.
QString ew::app::TimelineTick::label
The text to draw – already unit-appropriate ("1250 SA", "Harvestmoon", "06:15"), never a full date. Writing a whole date at every gradation is what made the old ruler unreadable.
Enumerations
enum class TimelineTickUnit { Minute, Hour, Day, Month, Year }
The unit a timeline's ruler is stepping in at the current zoom. Coarsening from Minute to Year as the view widens is what TL-15 means by an adaptive ruler.
Functions
std::vector< bool > ew::app::keepNonOverlappingLabels(const std::vector< RulerLabel > &labels, int minGap)
Given labels in ascending left-edge order (the natural ruler / elision-break draw order), returns for each whether to draw it so that no two KEPT labels come within minGap pixels of each other – a leftmost-wins sweep. This is meant to run on the FINAL pixel positions, after any axis elision has compressed the view, so labels never overprint at any zoom – including where a collapsed void squeezes many gradations (or many "N years hidden" break markers) together, the crowding TL-15's real-space tick spacing cannot foresee and TL-26 forbids. Pure geometry: the panel draws only the labels this keeps. minGap below zero is treated as zero.
TimelineRuler ew::app::timelineRuler(const ew::core::Calendar &calendar, qint64 viewStart, qint64 viewEnd, int viewportWidth, const TimelineLabelWidths &labelWidths)
Builds the ruler for the span [viewStart, viewEnd) drawn across viewportWidth pixels, in calendar, giving every gradation at least the room labelWidths says its unit needs.
Every gradation lands on a round value in the project's own calendar – the first of a month, midnight, a year divisible by the step – chosen from a 1/2/5 ladder that coarsens Minute → Hour → Day → Month → Year as the view widens.
A rung is used only if its gradations and its major band's gradations are far enough apart to fit their own labels. Testing both bands is not belt-and-braces: sizing only the minor band picks Hour/12 for an eleven-day view on a 1092px dock – twenty-two perfectly legible "00:00"s – and then hands the major band eleven "25 Harvestmoon" labels, 168px each, into 99px slots. That trades a thin ruler for a smeared one. A rung is admissible only if the whole ruler it implies is legible.
This exists because the old ruler was the visible span cut into four equal pieces (kAxisTicks = 5), whatever those happened to land on: it produced gradations like "1 Highsun, Year 1206 AC, 18:00" – not round, not adaptive, single-tier, and carrying a time of day on a view spanning ninety years (docs/TIMELINE_AUDIT.md TA-3). Note that the stray "18:00" was Calendar::format behaving correctly on a non-midnight instant; picking round instants removes it at the source, because a date at midnight formats without a time.
Each band's count is bounded by viewportWidth divided by that band's own label width, so no zoom – however absurd – can ask for a runaway number of ticks.
QString ew::app::widestContextSample(const ew::core::Calendar &calendar, TimelineTickUnit minorUnit)
The widest context label that can appear above a minorUnit ruler in calendar – what that unit's own labels leave out. Empty for Year, which has no context band (see TimelineRuler::major).
QString ew::app::widestLabelSample(const ew::core::Calendar &calendar, TimelineTickUnit unit)
The widest label unit can emit for calendar – a sample string, for the caller to measure.
This lives beside the code that formats the labels because it is the same knowledge: a Day gradation reads "25", so its widest case is a two-digit day. Month names are project data – a conlang month can be far longer than any English one – so no compile-time guess is safe, and only the caller can measure a font. Keeping the sample next to timelineRuler's own labelling is what stops the two drifting apart, which is exactly how a ruler starts reserving room for the wrong string.