| 294 |
294 |
|
//!
|
| 295 |
295 |
|
//! # Where the description stops
|
| 296 |
296 |
|
//!
|
| 297 |
|
- |
//! A day-plan timeline, a kanban board and a calendar are not describable here
|
| 298 |
|
- |
//! and will not become describable. A description expressive enough to produce
|
| 299 |
|
- |
//! a timeline is a component library wearing a description's name. Generate the
|
| 300 |
|
- |
//! boring 80% so the bespoke 20% gets the attention.
|
|
297 |
+ |
//! The rule is that a member is added when an app needs a fact the vocabulary
|
|
298 |
+ |
//! cannot state, and refused when what it wants is presentation it should be
|
|
299 |
+ |
//! asking a renderer for. That is the whole test. It is not a quota, and the
|
|
300 |
+ |
//! goal is every screen described.
|
| 301 |
301 |
|
//!
|
| 302 |
|
- |
//! [`Region::Bespoke`] is how that limit is stated rather than hidden. The
|
|
302 |
+ |
//! ## What the timeline refusal got wrong, 2026-08-15
|
|
303 |
+ |
//!
|
|
304 |
+ |
//! This section used to read "a day-plan timeline, a kanban board and a
|
|
305 |
+ |
//! calendar are not describable here and will not become describable", and it
|
|
306 |
+ |
//! propagated: 12 files across three apps, three libraries and the design wiki
|
|
307 |
+ |
//! cited it, including audiofiles and the MNW server, neither of which has a
|
|
308 |
+ |
//! timeline. It is withdrawn, and [`Track`] is the member it was refusing.
|
|
309 |
+ |
//!
|
|
310 |
+ |
//! The error was pricing. The argument assumed a timeline needs a component
|
|
311 |
+ |
//! library's worth of vocabulary, and nobody measured it. Held against
|
|
312 |
+ |
//! goingson's `day-planning-render.js`, the members it actually needed and
|
|
313 |
+ |
//! could not get were two integers: where a thing starts, and how long it
|
|
314 |
+ |
//! lasts. Labels, gridlines, item bodies and tones were all furniture this
|
|
315 |
+ |
//! crate already named. A refusal that expensive should have carried a
|
|
316 |
+ |
//! measurement, and did not.
|
|
317 |
+ |
//!
|
|
318 |
+ |
//! The reasoning underneath it survives and is still the test: slot heights,
|
|
319 |
+ |
//! gridline colour, how overlapping things stack, which hour scrolls into view.
|
|
320 |
+ |
//! Those are presentation, they stay the renderer's, and [`Track`] carries none
|
|
321 |
+ |
//! of them. What changed is the conclusion, not the principle.
|
|
322 |
+ |
//!
|
|
323 |
+ |
//! A kanban board and a calendar have not been measured the same way. They are
|
|
324 |
+ |
//! open questions rather than settled refusals, and whoever needs one should
|
|
325 |
+ |
//! count the missing members first.
|
|
326 |
+ |
//!
|
|
327 |
+ |
//! [`Region::Bespoke`] remains for the genuinely app-owned, and its
|
|
328 |
+ |
//! justification does not depend on the withdrawn claim. The
|
| 303 |
329 |
|
//! description names the *place* and the app owns the contents, so a screen
|
| 304 |
330 |
|
//! containing a timeline is still a whole screen and still routable. Without
|
| 305 |
331 |
|
//! it, the four goingson screens that make the app worth using would need a
|
| 1488 |
1514 |
|
}
|
| 1489 |
1515 |
|
}
|
| 1490 |
1516 |
|
|
|
1517 |
+ |
/// A window of time, in minutes from the start of a day.
|
|
1518 |
+ |
///
|
|
1519 |
+ |
/// The axis a [`Track`] draws. Minutes rather than instants, and offsets rather
|
|
1520 |
+ |
/// than wall-clock, because a description that carried a `DateTime` would carry
|
|
1521 |
+ |
/// a timezone with it and the vocabulary has no business holding one. The app
|
|
1522 |
+ |
/// knows which day this is; the description says how far along it a thing sits.
|
|
1523 |
+ |
///
|
|
1524 |
+ |
/// `to` is exclusive and may exceed `1440`, which is how a span that runs past
|
|
1525 |
+ |
/// midnight is said without a second date: `Span::new(1320, 1560)` is 22:00 to
|
|
1526 |
+ |
/// 02:00.
|
|
1527 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
1528 |
+ |
pub struct Span {
|
|
1529 |
+ |
from: u16,
|
|
1530 |
+ |
to: u16,
|
|
1531 |
+ |
}
|
|
1532 |
+ |
|
|
1533 |
+ |
impl Span {
|
|
1534 |
+ |
/// Midnight to midnight, the ordinary day.
|
|
1535 |
+ |
pub const DAY: Self = Self { from: 0, to: 1440 };
|
|
1536 |
+ |
|
|
1537 |
+ |
/// A span, clamped to a sane one.
|
|
1538 |
+ |
///
|
|
1539 |
+ |
/// An empty or backwards span is a caller bug that should not cost a
|
|
1540 |
+ |
/// renderer a division by zero, so `to` is forced at least one minute past
|
|
1541 |
+ |
/// `from` rather than returning an error nobody can act on. Same reasoning
|
|
1542 |
+ |
/// as [`Share::percent`], which clamps rather than refuses.
|
|
1543 |
+ |
#[must_use]
|
|
1544 |
+ |
pub const fn new(from: u16, to: u16) -> Self {
|
|
1545 |
+ |
Self {
|
|
1546 |
+ |
from,
|
|
1547 |
+ |
to: if to > from { to } else { from + 1 },
|
|
1548 |
+ |
}
|
|
1549 |
+ |
}
|
|
1550 |
+ |
|
|
1551 |
+ |
/// The first minute on the axis.
|
|
1552 |
+ |
#[must_use]
|
|
1553 |
+ |
pub const fn from(self) -> u16 {
|
|
1554 |
+ |
self.from
|
|
1555 |
+ |
}
|
|
1556 |
+ |
|
|
1557 |
+ |
/// One past the last minute on the axis.
|
|
1558 |
+ |
#[must_use]
|
|
1559 |
+ |
pub const fn to(self) -> u16 {
|
|
1560 |
+ |
self.to
|
|
1561 |
+ |
}
|
|
1562 |
+ |
|
|
1563 |
+ |
/// How many minutes the axis covers. Never zero.
|
|
1564 |
+ |
#[must_use]
|
|
1565 |
+ |
pub const fn minutes(self) -> u16 {
|
|
1566 |
+ |
self.to - self.from
|
|
1567 |
+ |
}
|
|
1568 |
+ |
|
|
1569 |
+ |
/// Whether a minute falls on this axis.
|
|
1570 |
+ |
#[must_use]
|
|
1571 |
+ |
pub const fn holds(self, minute: u16) -> bool {
|
|
1572 |
+ |
minute >= self.from && minute < self.to
|
|
1573 |
+ |
}
|
|
1574 |
+ |
}
|
|
1575 |
+ |
|
|
1576 |
+ |
impl Default for Span {
|
|
1577 |
+ |
fn default() -> Self {
|
|
1578 |
+ |
Self::DAY
|
|
1579 |
+ |
}
|
|
1580 |
+ |
}
|
|
1581 |
+ |
|
|
1582 |
+ |
/// Where a thing sits on a [`Track`], and for how long.
|
|
1583 |
+ |
///
|
|
1584 |
+ |
/// The one fact a list cannot carry and the whole reason this primitive exists.
|
|
1585 |
+ |
/// A list says what order things come in; a track says a thing starts 135
|
|
1586 |
+ |
/// minutes along and lasts 45, which is a different claim and not derivable
|
|
1587 |
+ |
/// from the first.
|
|
1588 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
1589 |
+ |
pub struct Placement {
|
|
1590 |
+ |
at: u16,
|
|
1591 |
+ |
minutes: u16,
|
|
1592 |
+ |
}
|
|
1593 |
+ |
|
|
1594 |
+ |
impl Placement {
|
|
1595 |
+ |
/// A placement, clamped to a drawable one.
|
|
1596 |
+ |
///
|
|
1597 |
+ |
/// Zero minutes becomes one for the same reason [`Span::new`] clamps: a
|
|
1598 |
+ |
/// zero-height thing is invisible rather than expressive, and every
|
|
1599 |
+ |
/// renderer would need its own guard.
|
|
1600 |
+ |
#[must_use]
|
|
1601 |
+ |
pub const fn new(at: u16, minutes: u16) -> Self {
|
|
1602 |
+ |
Self {
|
|
1603 |
+ |
at,
|
|
1604 |
+ |
minutes: if minutes == 0 { 1 } else { minutes },
|
|
1605 |
+ |
}
|
|
1606 |
+ |
}
|
|
1607 |
+ |
|
|
1608 |
+ |
/// Minutes from the start of the day, matching [`Span`]'s origin.
|
|
1609 |
+ |
#[must_use]
|
|
1610 |
+ |
pub const fn at(self) -> u16 {
|
|
1611 |
+ |
self.at
|
|
1612 |
+ |
}
|
|
1613 |
+ |
|
|
1614 |
+ |
/// How long it lasts. Never zero.
|
|
1615 |
+ |
#[must_use]
|
|
1616 |
+ |
pub const fn minutes(self) -> u16 {
|
|
1617 |
+ |
self.minutes
|
|
1618 |
+ |
}
|
|
1619 |
+ |
|
|
1620 |
+ |
/// One past its last minute.
|
|
1621 |
+ |
#[must_use]
|
|
1622 |
+ |
pub const fn end(self) -> u16 {
|
|
1623 |
+ |
self.at + self.minutes
|
|
1624 |
+ |
}
|
|
1625 |
+ |
|
|
1626 |
+ |
/// Whether two placements cover any of the same time.
|
|
1627 |
+ |
///
|
|
1628 |
+ |
/// Geometry, and deliberately not a described field. Whether an overlap is
|
|
1629 |
+ |
/// a *conflict* is the app's judgment -- a meeting inside a block of free
|
|
1630 |
+ |
/// time overlaps and is fine -- and that judgment travels the way every
|
|
1631 |
+ |
/// other judgment does, as a [`Tone`] on the thing itself. What a renderer
|
|
1632 |
+ |
/// needs in order to lay two things side by side instead of on top of each
|
|
1633 |
+ |
/// other is this, and it can compute it.
|
|
1634 |
+ |
///
|
|
1635 |
+ |
/// The alternative was a `conflicts: bool` on each entry, which is state
|
|
1636 |
+ |
/// that can disagree with the times beside it. Two sources for one fact is
|
|
1637 |
+ |
/// how a screen starts rendering a conflict badge on a thing that no longer
|
|
1638 |
+ |
/// conflicts.
|
|
1639 |
+ |
#[must_use]
|
|
1640 |
+ |
pub const fn overlaps(self, other: Self) -> bool {
|
|
1641 |
+ |
self.at < other.end() && other.at < self.end()
|
|
1642 |
+ |
}
|
|
1643 |
+ |
}
|
|
1644 |
+ |
|
|
1645 |
+ |
/// A time axis: things placed by when they happen, rather than flowed.
|
|
1646 |
+ |
///
|
|
1647 |
+ |
/// # Why this is a primitive
|
|
1648 |
+ |
///
|
|
1649 |
+ |
/// This crate refused to name it until 2026-08-15, on the argument that a
|
|
1650 |
+ |
/// description expressive enough to draw a timeline is a component library
|
|
1651 |
+ |
/// wearing a description's name. The refusal is withdrawn, and it is worth
|
|
1652 |
+ |
/// being precise about what was wrong with it, because the reasoning it used
|
|
1653 |
+ |
/// applies to real cases and should not be discarded with it.
|
|
1654 |
+ |
///
|
|
1655 |
+ |
/// What a timeline needs that a [`List`](Region::Pane) does not is **one**
|
|
1656 |
+ |
/// thing: placement. Where a thing sits is a fact about the thing, the way a
|
|
1657 |
+ |
/// row's primary text is, and it is not derivable from order. Everything else a
|
|
1658 |
+ |
/// day view draws -- the labels, the gridlines, the item bodies, the tones --
|
|
1659 |
+ |
/// is furniture this vocabulary already names. Measured against goingson's
|
|
1660 |
+ |
/// `day-planning-render.js`, the only members it needed and could not get were
|
|
1661 |
+ |
/// `at` and `minutes`.
|
|
1662 |
+ |
///
|
|
1663 |
+ |
/// So the timeline was never a component library's worth of vocabulary. It was
|
|
1664 |
+ |
/// two integers, and the refusal was priced as though it were the whole widget.
|
|
1665 |
+ |
/// The test that matters is not "does this shape look complicated" but "how
|
|
1666 |
+ |
/// many members does it actually add, and are they facts or presentation".
|
|
1667 |
+ |
/// Slot heights, gridline colour, how overlaps stack and which hour scrolls
|
|
1668 |
+ |
/// into view on open are all presentation and all stay the renderer's, which is
|
|
1669 |
+ |
/// why they are absent here.
|
|
1670 |
+ |
///
|
|
1671 |
+ |
/// # What it does not carry
|
|
1672 |
+ |
///
|
|
1673 |
+ |
/// No pixel measure, no scroll offset, no drag affordance. A renderer draws the
|
|
1674 |
+ |
/// span at whatever density its host uses; `makeover-geometry` owns that the
|
|
1675 |
+ |
/// way it owns everything else measured in pixels.
|
|
1676 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
1677 |
+ |
pub struct Track {
|
|
1678 |
+ |
/// The window the axis covers.
|
|
1679 |
+ |
pub span: Span,
|
|
1680 |
+ |
/// The granularity a thing can be placed on, in minutes.
|
|
1681 |
+ |
///
|
|
1682 |
+ |
/// goingson's day view is 15, giving 96 slots across a day. A renderer uses
|
|
1683 |
+ |
/// it to decide where gridlines fall and what a drop lands on; it does not
|
|
1684 |
+ |
/// constrain [`Placement`], because data arriving from a calendar does not
|
|
1685 |
+ |
/// respect anyone's grid.
|
|
1686 |
+ |
pub slot: u16,
|
|
1687 |
+ |
/// How often the axis labels itself, in minutes.
|
|
1688 |
+ |
///
|
|
1689 |
+ |
/// 60 gives an hourly ruler over a 15-minute grid, which is the common
|
|
1690 |
+ |
/// shape and the reason this is separate from `slot`. Zero means an
|
|
1691 |
+ |
/// unlabelled axis.
|
|
1692 |
+ |
pub tick: u16,
|
|
1693 |
+ |
}
|
|
1694 |
+ |
|
|
1695 |
+ |
impl Track {
|
|
1696 |
+ |
/// An ordinary day: midnight to midnight, quarter-hour slots, hourly ticks.
|
|
1697 |
+ |
pub const DAY: Self = Self {
|
|
1698 |
+ |
span: Span::DAY,
|
|
1699 |
+ |
slot: 15,
|
|
1700 |
+ |
tick: 60,
|
|
1701 |
+ |
};
|
|
1702 |
+ |
|
|
1703 |
+ |
/// A track over `span`, with the day's usual granularity.
|
|
1704 |
+ |
#[must_use]
|
|
1705 |
+ |
pub const fn over(span: Span) -> Self {
|
|
1706 |
+ |
Self {
|
|
1707 |
+ |
span,
|
|
1708 |
+ |
slot: 15,
|
|
1709 |
+ |
tick: 60,
|
|
1710 |
+ |
}
|
|
1711 |
+ |
}
|
|
1712 |
+ |
|
|
1713 |
+ |
/// How many slots the axis holds.
|
|
1714 |
+ |
///
|
|
1715 |
+ |
/// Rounded up, so a span that does not divide evenly by `slot` still has a
|
|
1716 |
+ |
/// slot covering its tail rather than dropping it. Never zero: `slot` of 0
|
|
1717 |
+ |
/// reads as one slot spanning the whole axis rather than a division by
|
|
1718 |
+ |
/// zero, since a renderer asking this question has already committed to
|
|
1719 |
+ |
/// drawing something.
|
|
1720 |
+ |
#[must_use]
|
|
1721 |
+ |
pub const fn slots(self) -> u16 {
|
|
1722 |
+ |
if self.slot == 0 {
|
|
1723 |
+ |
1
|
|
1724 |
+ |
} else {
|
|
1725 |
+ |
self.span.minutes().div_ceil(self.slot)
|
|
1726 |
+ |
}
|
|
1727 |
+ |
}
|
|
1728 |
+ |
|
|
1729 |
+ |
/// Where a placement sits on the axis, as a fraction from 0.0 to 1.0.
|
|
1730 |
+ |
///
|
|
1731 |
+ |
/// The one calculation every renderer would otherwise write itself, and the
|
|
1732 |
+ |
/// place the three would drift apart. Clamped, so a placement outside the
|
|
1733 |
+ |
/// span draws at the edge rather than off it -- an event running past
|
|
1734 |
+ |
/// midnight is a real thing and truncating it is better than either
|
|
1735 |
+ |
/// panicking or drawing it somewhere impossible.
|
|
1736 |
+ |
#[must_use]
|
|
1737 |
+ |
pub fn fraction(self, minute: u16) -> f32 {
|
|
1738 |
+ |
let span = f32::from(self.span.minutes());
|
|
1739 |
+ |
let offset = f32::from(minute.saturating_sub(self.span.from()));
|
|
1740 |
+ |
(offset / span).clamp(0.0, 1.0)
|
|
1741 |
+ |
}
|
|
1742 |
+ |
}
|
|
1743 |
+ |
|
|
1744 |
+ |
impl Default for Track {
|
|
1745 |
+ |
fn default() -> Self {
|
|
1746 |
+ |
Self::DAY
|
|
1747 |
+ |
}
|
|
1748 |
+ |
}
|
|
1749 |
+ |
|
| 1491 |
1750 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 1492 |
1751 |
|
pub struct Act<'a> {
|
| 1493 |
1752 |
|
/// What the control says.
|
| 1644 |
1903 |
|
///
|
| 1645 |
1904 |
|
/// # What it does not do
|
| 1646 |
1905 |
|
///
|
| 1647 |
|
- |
/// It does not make a timeline describable, and the refusal in the crate
|
| 1648 |
|
- |
/// header stands unchanged. A widget is an assembly of things the
|
| 1649 |
|
- |
/// vocabulary *already* says; anything that needs a member the vocabulary
|
| 1650 |
|
- |
/// does not have is a finding about the vocabulary or it is
|
| 1651 |
|
- |
/// [`Bespoke`](Self::Bespoke). A widget is never the way a primitive gets
|
| 1652 |
|
- |
/// added by the back door.
|
|
1906 |
+ |
/// A widget is an assembly of things the vocabulary *already* says, so it
|
|
1907 |
+ |
/// buys no expressive power. Anything needing a member the vocabulary does
|
|
1908 |
+ |
/// not have is a finding about the vocabulary, and the answer to a finding
|
|
1909 |
+ |
/// is to add the member. A widget is never the way a primitive gets added
|
|
1910 |
+ |
/// by the back door.
|
|
1911 |
+ |
///
|
|
1912 |
+ |
/// This used to say "it does not make a timeline describable, and the
|
|
1913 |
+ |
/// refusal in the crate header stands unchanged". The timeline is
|
|
1914 |
+ |
/// describable as of 2026-08-15 -- see [`Track`] -- and it got there the
|
|
1915 |
+ |
/// way the paragraph above says it should have: by adding the two members
|
|
1916 |
+ |
/// that were missing, not by dressing the screen up as an assembly.
|
| 1653 |
1917 |
|
Widget {
|
| 1654 |
1918 |
|
/// What the assembly is called. This crate never interprets it, and a
|
| 1655 |
1919 |
|
/// renderer is free not to know it.
|
| 3157 |
3421 |
|
assert!(!Selector::Toggle.abutting());
|
| 3158 |
3422 |
|
}
|
| 3159 |
3423 |
|
|
|
3424 |
+ |
#[test]
|
|
3425 |
+ |
fn a_span_never_has_zero_minutes_however_it_is_asked_for() {
|
|
3426 |
+ |
// Every renderer divides by this. A caller passing a backwards or empty
|
|
3427 |
+ |
// span is a bug, but it is not a bug worth a panic three renderers deep.
|
|
3428 |
+ |
assert_eq!(Span::new(600, 600).minutes(), 1);
|
|
3429 |
+ |
assert_eq!(Span::new(600, 300).minutes(), 1);
|
|
3430 |
+ |
assert_eq!(Span::DAY.minutes(), 1440);
|
|
3431 |
+ |
}
|
|
3432 |
+ |
|
|
3433 |
+ |
#[test]
|
|
3434 |
+ |
fn a_span_can_run_past_midnight_without_a_second_date() {
|
|
3435 |
+ |
// 22:00 to 02:00. The alternative was carrying a date, which drags a
|
|
3436 |
+ |
// timezone into the vocabulary for the sake of one night shift.
|
|
3437 |
+ |
let overnight = Span::new(1320, 1560);
|
|
3438 |
+ |
assert_eq!(overnight.minutes(), 240);
|
|
3439 |
+ |
assert!(overnight.holds(1500));
|
|
3440 |
+ |
assert!(!overnight.holds(1200));
|
|
3441 |
+ |
}
|
|
3442 |
+ |
|
|
3443 |
+ |
#[test]
|
|
3444 |
+ |
fn overlap_is_computed_rather_than_declared() {
|
|
3445 |
+ |
// The reason Placement carries no `conflicts` flag: the times already
|
|
3446 |
+ |
// say it, and a second source for one fact is how a stale conflict
|
|
3447 |
+ |
// badge outlives the conflict.
|
|
3448 |
+ |
let morning = Placement::new(540, 60); // 09:00-10:00
|
|
3449 |
+ |
let overlapping = Placement::new(570, 60); // 09:30-10:30
|
|
3450 |
+ |
let after = Placement::new(600, 60); // 10:00-11:00
|
|
3451 |
+ |
|
|
3452 |
+ |
assert!(morning.overlaps(overlapping));
|
|
3453 |
+ |
assert!(overlapping.overlaps(morning), "overlap is symmetric");
|
|
3454 |
+ |
// Touching end to end is not overlapping: `to` is exclusive, so a
|
|
3455 |
+ |
// 10:00 start does not collide with a 10:00 end.
|
|
3456 |
+ |
assert!(!morning.overlaps(after));
|
|
3457 |
+ |
assert!(!after.overlaps(morning));
|
|
3458 |
+ |
}
|
|
3459 |
+ |
|
|
3460 |
+ |
#[test]
|
|
3461 |
+ |
fn a_placement_is_always_drawable() {
|
|
3462 |
+ |
assert_eq!(Placement::new(540, 0).minutes(), 1);
|
|
3463 |
+ |
assert_eq!(Placement::new(540, 30).end(), 570);
|
|
3464 |
+ |
}
|
|
3465 |
+ |
|
|
3466 |
+ |
#[test]
|
|
3467 |
+ |
fn a_track_places_the_fraction_every_renderer_would_otherwise_compute() {
|
|
3468 |
+ |
let day = Track::DAY;
|
|
3469 |
+ |
assert!((day.fraction(0) - 0.0).abs() < f32::EPSILON);
|
|
3470 |
+ |
assert!((day.fraction(720) - 0.5).abs() < f32::EPSILON);
|
|
3471 |
+ |
// Clamped rather than off the end: an event running past the span's
|
|
3472 |
+ |
// close draws at the edge, which beats panicking or drawing nowhere.
|
|
3473 |
+ |
assert!((day.fraction(2000) - 1.0).abs() < f32::EPSILON);
|
|
3474 |
+ |
}
|
|
3475 |
+ |
|
|
3476 |
+ |
#[test]
|
|
3477 |
+ |
fn a_track_counts_its_slots_and_never_divides_by_zero() {
|
|
3478 |
+ |
assert_eq!(Track::DAY.slots(), 96);
|
|
3479 |
+ |
assert_eq!(Track::over(Span::new(540, 1020)).slots(), 32);
|
|
3480 |
+ |
// A span that does not divide evenly keeps a slot for its tail.
|
|
3481 |
+ |
assert_eq!(Track::over(Span::new(0, 50)).slots(), 4);
|
|
3482 |
+ |
// slot: 0 is a caller bug that reads as one slot, not a panic.
|
|
3483 |
+ |
let degenerate = Track {
|
|
3484 |
+ |
span: Span::DAY,
|
|
3485 |
+ |
slot: 0,
|
|
3486 |
+ |
tick: 60,
|
|
3487 |
+ |
};
|
|
3488 |
+ |
assert_eq!(degenerate.slots(), 1);
|
|
3489 |
+ |
}
|
|
3490 |
+ |
|
|
3491 |
+ |
#[test]
|
|
3492 |
+ |
fn a_track_carries_facts_and_no_presentation() {
|
|
3493 |
+ |
// The guard on the thing the withdrawn refusal was right about. If a
|
|
3494 |
+ |
// pixel measure, a scroll offset or a colour ever lands on Track, the
|
|
3495 |
+ |
// member has stopped being a fact about the data and the timeline
|
|
3496 |
+ |
// really has become a component library wearing a description's name.
|
|
3497 |
+ |
let day = Track::DAY;
|
|
3498 |
+ |
assert_eq!(day.span, Span::DAY);
|
|
3499 |
+ |
assert_eq!(day.slot, 15);
|
|
3500 |
+ |
assert_eq!(day.tick, 60);
|
|
3501 |
+ |
// Three fields. Adding a fourth should have to come here and say why.
|
|
3502 |
+ |
assert_eq!(std::mem::size_of::<Track>(), 8);
|
|
3503 |
+ |
}
|
|
3504 |
+ |
|
| 3160 |
3505 |
|
#[test]
|
| 3161 |
3506 |
|
fn a_pane_is_looked_into_and_a_band_is_not() {
|
| 3162 |
3507 |
|
assert_eq!(Region::Pane.depth(), Depth::Well);
|