Skip to content

Timeline

textual_timepiece.timeline

Widgets for displaying & interacting with data on a timeline scale.

CLASS DESCRIPTION
VerticalTimeline

Timeline that displays entries in a vertical view.

VerticalEntry

Vertical entry for a vertical timeline layout.

VerticalTimelineNavigation

Vertical widget containing a vertical timeline and header.

VerticalRuler

Vertical ruler for marking vertical timelines.

RuledVerticalTimeline

Ruled vertical timeline with markers.

HorizontalTimeline

Timeline that displays entries in a horizontal view.

HorizontalEntry

Horizontal entry for a horizontal timeline layout.

HorizontalTimelineNavigation

Horizontal widget containing a horizontal timeline and header.

HorizontalRuler

Horizontal ruler for marking horizontal timelines.

RuledHorizontalTimeline

Ruled horizontal timeline with markers.

VerticalTimeline

Bases: AbstractTimeline[VerticalEntryType]

Timeline that displays entries in a vertical view.

PARAMETER DESCRIPTION
*children

Entries to intially add to the widget.

TYPE: EntryType DEFAULT: ()

duration

Size of the widget.

TYPE: int | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

tile

Whether to tile the timeline or not. Set to False if a single column of entries is preferred.

TYPE: bool DEFAULT: True

CLASS DESCRIPTION
Updated

Base class for all timeline messages.

Created

Sent when a new entry is created.

Deleted

Sent when an entry is deleted.

Selected

Sent when a new entry selected.

METHOD DESCRIPTION
action_delete_entry

Remove the selected or provided entry from the timeline.

action_adjust_tail

Adjust the tail of the selected timeline entry.

action_adjust_head

Adjust the head of the selected timeline entry.

refresh_line

Refresh a single line.

ATTRIBUTE DESCRIPTION
BINDINGS

All bindings for AbstractTimeline widget.

TYPE: list[BindingType]

COMPONENT_CLASSES

All component classes for the AbstractTimeline widget.

TYPE: set[str]

length

Actual size of the widget with the direction size of the widget.

markers

Custom markers to place on the timeline.

tile

Is calendar tiling enabled?

TYPE: bool

selected

Currently highlighted entry. None if there is nothing selected.

TYPE: EntryType | None

DEFAULT_CSS

Default CSS for VerticalTimeline widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
class VerticalTimeline(AbstractTimeline[VerticalEntryType]):
    """Timeline that displays entries in a vertical view.

    Params:
        *children: Entries to intially add to the widget.
        duration: Size of the widget.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
        tile: Whether to tile the timeline or not. Set to `False` if a single
            column of entries is preferred.
    """

    Entry = VerticalEntry  # type: ignore[assignment] # FIX: Need to research to how to correctly accomplish this.
    Layout = VerticalTimelineLayout
    DURATION = "height"
    DEFAULT_CSS: ClassVar[str] = """\
    VerticalTimeline {
        height: auto !important;
        border-left: wide $secondary;
        border-right: wide $secondary;
        &:hover {
            border-left: thick $secondary;
            border-right: thick $secondary;
        }
        &:focus {
            border-left: outer $primary;
            border-right: outer $primary;
        }
    }
    """
    """Default CSS for `VerticalTimeline` widget."""

    class Created(
        AbstractTimeline.Created[VerticalEntryT, VerticalTimelineType]
    ):
        """Sent when a new entry is created."""

    class Deleted(
        AbstractTimeline.Deleted[VerticalEntryT, VerticalTimelineType]
    ):
        """Sent when an entry is deleted."""

    class Selected(
        AbstractTimeline.Selected[VerticalEntryT, VerticalTimelineType]
    ):
        """Sent when a new entry selected."""

    def _watch_markers(
        self,
        old: AbstractTimeline.Markers,
        new: AbstractTimeline.Markers,
    ) -> None:
        for line in old.keys() ^ new.keys():
            self.refresh_line(line)

    def refresh_line(self, y: int) -> None:
        """Refresh a single line.

        Args:
            y: Coordinate of line.
        """
        self.refresh(
            Region(
                0,
                y - self.scroll_offset.y,
                max(self.virtual_size.width, self.size.width),
                1,
            )
        )

    def render_lines(self, crop: Region) -> list[Strip]:
        self._basic_strip = Strip(
            [
                Segment(
                    "─" * self.size.width,
                    style=self.get_component_rich_style("timeline--normal"),
                )
            ]
        )
        return super().render_lines(crop)

    def render_line(self, y: int) -> Strip:
        if marker := self.markers.get(y):
            style, label = marker

            return Strip(
                [Segment(label.center(self.size.width, "─"), style=style)]
            )

        return self._basic_strip

    def _calc_entry_size(self, end: Offset) -> tuple[int, int]:
        start = cast("Offset", self._start)
        return start.y if start.y < end.y else end.y, abs(end.y - start.y)

    def pre_layout(self, layout: VerticalTimelineLayout) -> None:  # type: ignore[override]
        # Sorting with the public sort method will cause infinite layout calls.
        self._nodes._sort(  # TODO: This needs a public solution.
            key=lambda w: (w.offset.y, cast("Scalar", w.styles.height).value),
        )

    def get_content_height(
        self,
        container: Size,
        viewport: Size,
        width: int,
    ) -> int:
        return self.length

BINDINGS class-attribute

BINDINGS: list[BindingType] = [
    Binding(
        "ctrl+down,ctrl+right",
        "adjust_tail",
        tooltip="Move entry to the backward.",
    ),
    Binding(
        "ctrl+up,ctrl+left",
        "adjust_head",
        tooltip="Move entry to the forward.",
    ),
    Binding(
        "alt+shift+down,alt+shift+left",
        "adjust_tail(True, True)",
        tooltip="Resize the tail end of the entry.",
    ),
    Binding(
        "alt+shift+up,alt+shift+right",
        "adjust_head(True, True)",
        tooltip="Resize the end of the entry forward.",
    ),
    Binding(
        "shift+up,shift+left",
        "adjust_head(False, True)",
        tooltip="Resize the start of the entry backward.",
    ),
    Binding(
        "shift+down,shift+right",
        "adjust_tail(False, True)",
        tooltip="Move the head of the entry forward.",
    ),
    Binding(
        "ctrl+d,delete,backspace",
        "delete_entry",
        "Delete Entry",
        tooltip="Delete the selected entry.",
    ),
    Binding(
        "escape",
        "clear_active",
        "Clear",
        priority=True,
        show=False,
        tooltip="Cancel creating an entry or deselect entries.",
    ),
]

All bindings for AbstractTimeline widget.

Key(s) Description
ctrl+down,ctrl+right Move entry to the backward.
ctrl+up,ctrl+left Move entry to the forward.
alt+shift+down,alt+shift+left Resize the tail end of the entry.
alt+shift+up,alt+shift+right Resize the end of the entry forward.
shift+up,shift+left Resize the start of the entry backward.
shift+down,shift+right Move the head of the entry forward.
ctrl+d,delete,backspace Delete the selected entry.
escape Cancel creating an entry or deselect entries.

COMPONENT_CLASSES class-attribute

COMPONENT_CLASSES: set[str] = {'timeline--normal'}

All component classes for the AbstractTimeline widget.

Class Description
timeline--normal Target all lines without set markers.

length class-attribute instance-attribute

length = Reactive[int](96, init=False, layout=True)

Actual size of the widget with the direction size of the widget.

markers class-attribute instance-attribute

markers = Reactive[Markers](
    MappingProxyType({}), init=False, compute=False, repaint=False
)

Custom markers to place on the timeline.

tile property writable

tile: bool

Is calendar tiling enabled?

selected property

selected: EntryType | None

Currently highlighted entry. None if there is nothing selected.

DEFAULT_CSS class-attribute

VerticalTimeline {
    height: auto !important;
    border-left: wide $secondary;
    border-right: wide $secondary;
    &:hover {
        border-left: thick $secondary;
        border-right: thick $secondary;
    }
    &:focus {
        border-left: outer $primary;
        border-right: outer $primary;
    }
}

Default CSS for VerticalTimeline widget.

Updated

Bases: BaseMessage[TimelineType], Generic[EntryT, TimelineType]

Base class for all timeline messages.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
81
82
83
84
85
86
87
88
89
90
91
92
93
class Updated(BaseMessage[TimelineType], Generic[EntryT, TimelineType]):
    """Base class for all timeline messages."""

    def __init__(self, widget: TimelineType, entry: EntryT) -> None:
        super().__init__(widget)

        self.entry = entry
        """Entry that was updated."""

    @property
    def timeline(self) -> TimelineType:
        """Alias for `widget` attribute."""
        return self.widget
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Created

Bases: Created[VerticalEntryT, VerticalTimelineType]

Sent when a new entry is created.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
454
455
456
457
class Created(
    AbstractTimeline.Created[VerticalEntryT, VerticalTimelineType]
):
    """Sent when a new entry is created."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Deleted

Bases: Deleted[VerticalEntryT, VerticalTimelineType]

Sent when an entry is deleted.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
459
460
461
462
class Deleted(
    AbstractTimeline.Deleted[VerticalEntryT, VerticalTimelineType]
):
    """Sent when an entry is deleted."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Selected

Bases: Selected[VerticalEntryT, VerticalTimelineType]

Sent when a new entry selected.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
464
465
466
467
class Selected(
    AbstractTimeline.Selected[VerticalEntryT, VerticalTimelineType]
):
    """Sent when a new entry selected."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

action_delete_entry

action_delete_entry(id: str | None = None) -> None

Remove the selected or provided entry from the timeline.

PARAMETER DESCRIPTION
id

If removing an un-highlighted widget.

TYPE: str | None DEFAULT: None

Source code in src/textual_timepiece/timeline/_base_timeline.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def action_delete_entry(self, id: str | None = None) -> None:
    """Remove the selected or provided entry from the timeline.

    Args:
        id: If removing an un-highlighted widget.
    """
    if id:
        try:
            entry = self.query_one(f"#{id}", self.Entry)
        except NoMatches:
            return
    elif self.selected:
        entry = self.selected
    else:
        return

    entry.remove()
    self.post_message(self.Deleted(self, entry))

action_adjust_tail

action_adjust_tail(tail: bool = False, resize: bool = False) -> None

Adjust the tail of the selected timeline entry.

PARAMETER DESCRIPTION
tail

Increase the size if resizing.

TYPE: bool DEFAULT: False

resize

Resize the entry instead of moving.

TYPE: bool DEFAULT: False

Source code in src/textual_timepiece/timeline/_base_timeline.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
def action_adjust_tail(
    self,
    tail: bool = False,
    resize: bool = False,
) -> None:
    """Adjust the tail of the selected timeline entry.

    Args:
        tail: Increase the size if resizing.
        resize: Resize the entry instead of moving.
    """
    if resize:
        cast("EntryType", self.selected).resize(1, tail=tail)
    else:
        cast("EntryType", self.selected).move(1)

action_adjust_head

action_adjust_head(tail: bool = False, resize: bool = False) -> None

Adjust the head of the selected timeline entry.

PARAMETER DESCRIPTION
tail

Increase the size if resizing.

TYPE: bool DEFAULT: False

resize

Resize the entry instead of moving.

TYPE: bool DEFAULT: False

Source code in src/textual_timepiece/timeline/_base_timeline.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def action_adjust_head(
    self,
    tail: bool = False,
    resize: bool = False,
) -> None:
    """Adjust the head of the selected timeline entry.

    Args:
        tail: Increase the size if resizing.
        resize: Resize the entry instead of moving.
    """
    if resize:
        cast("EntryType", self.selected).resize(-1, tail=not tail)
    else:
        cast("EntryType", self.selected).move(-1)

refresh_line

refresh_line(y: int) -> None

Refresh a single line.

PARAMETER DESCRIPTION
y

Coordinate of line.

TYPE: int

Source code in src/textual_timepiece/timeline/_base_timeline.py
477
478
479
480
481
482
483
484
485
486
487
488
489
490
def refresh_line(self, y: int) -> None:
    """Refresh a single line.

    Args:
        y: Coordinate of line.
    """
    self.refresh(
        Region(
            0,
            y - self.scroll_offset.y,
            max(self.virtual_size.width, self.size.width),
            1,
        )
    )

VerticalEntry

Bases: AbstractEntry

Vertical entry for a vertical timeline layout.

PARAMETER DESCRIPTION
id

ID of the Widget.

TYPE: str

content

A Rich renderable, or string containing console markup.

TYPE: RenderableType | SupportsVisual DEFAULT: ''

offset

Initial offset to use for the entry.

TYPE: int | None DEFAULT: None

size

Initial size to use for the entry.

TYPE: int | None DEFAULT: None

markup

True if markup should be parsed and rendered.

TYPE: bool DEFAULT: True

name

Name of widget.

TYPE: str | None DEFAULT: None

classes

Space separated list of class names.

TYPE: str | None DEFAULT: None

disabled

Whether the static is disabled or not.

TYPE: bool DEFAULT: False

CLASS DESCRIPTION
Updated

Base message for all entries.

Resized

Entry was resized by the user.

Moved

Entry was moved by the user.

METHOD DESCRIPTION
mime

Instatiate a mime entry. For previewing resizing visually.

resize

Public method for resizing the widget.

is_moving

Is the widget moving or resizing?

ATTRIBUTE DESCRIPTION
clicked

Initial point where the entry was clicked when moving or resizing.

dimension

Alias for the size of the entry depending on orientation.

TYPE: int

DEFAULT_CSS

Default CSS for VerticalEntry widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_timeline_entry.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
class VerticalEntry(AbstractEntry):
    """Vertical entry for a vertical timeline layout.

    Params:
        id: ID of the Widget.
        content: A Rich renderable, or string containing console markup.
        offset: Initial offset to use for the entry.
        size: Initial size to use for the entry.
        markup: True if markup should be parsed and rendered.
        name: Name of widget.
        classes: Space separated list of class names.
        disabled: Whether the static is disabled or not.
    """

    class Resized(AbstractEntry.Resized["VerticalEntry"]):
        """Entry was resized by the user."""

    class Moved(AbstractEntry.Moved["VerticalEntry"]):
        """Entry was moved by the user."""

    parent: VerticalTimeline
    DIMENSION = "height"
    EDGE_MARGIN: ClassVar[int] = 1
    DEFAULT_CSS: ClassVar[str] = """\
    VerticalEntry {
        width: 100%;
        min-height: 2;
        height: 2;
        border-top: double $panel-lighten-2;
        border-bottom: double $panel-lighten-2;
        &:hover.size_start {
            border: none;
            border-top: double $secondary;
            border-bottom: double $panel-lighten-2;
        }
        &:hover.size_end {
            border: none;
            border-bottom: double $secondary;
            border-top: double $panel-lighten-2;
        }
        &:focus.size_start {
            border: none;
            border-top: thick $primary;
            border-bottom: double $primary;
        }
        &:focus.size_end {
            border: none;
            border-bottom: thick $primary;
            border-top: double $primary;
        }
        &:focus {
            border: double $primary;
        }
        &.-mime {
            hatch: horizontal white 5%;
        }
    }
    """
    """Default CSS for `VerticalEntry` widget."""

    def sel_delta(self, event: MouseEvent) -> int:
        return event.delta_y

    def is_tail(self, offset: Offset) -> bool:
        return 0 <= offset.y <= self.EDGE_MARGIN

    def is_head(self, offset: Offset) -> bool:
        height = cast("Scalar", self.styles.height).value
        return height - self.EDGE_MARGIN <= offset.y <= height

    def is_start(self, offset: Offset) -> bool:
        return offset.y < cast("Scalar", self.styles.height).value / 2

    def move(self, delta: int) -> None:
        offset = Offset(0, delta)
        self.offset = self.offset + offset
        self.post_message(self.Moved(self, delta))

    def merge(self, other: VerticalEntry) -> AwaitRemove:
        y1, y2 = self.offset.y, other.offset.y
        start = y1 if y1 <= y2 else y2
        self.offset = Offset(0, start)
        e1, e2 = self.end, other.end
        self.styles.height = abs(start - (e1 if e1 >= e2 else e2))
        return other.remove()

    def _resize_helper(self, delta: int, *, tail: bool) -> int:
        if tail:
            delta *= -1

        new_height = cast("Scalar", self.styles.height).value + delta

        if tail and new_height != cast("Scalar", self.styles.height).value:
            self.offset += Offset(0, -delta)

        self.styles.height = new_height
        return delta

    def set_dims(
        self,
        offset: int | None = None,
        size: int | None = None,
    ) -> None:
        if offset:
            self.offset = Offset(0, offset)
        if size:
            self.styles.height = size

    @property
    def start(self) -> int:
        return self.offset.y

clicked class-attribute instance-attribute

clicked = var[Offset | None](None, init=False)

Initial point where the entry was clicked when moving or resizing.

dimension property writable

dimension: int

Alias for the size of the entry depending on orientation.

DEFAULT_CSS class-attribute

VerticalEntry {
    width: 100%;
    min-height: 2;
    height: 2;
    border-top: double $panel-lighten-2;
    border-bottom: double $panel-lighten-2;
    &:hover.size_start {
        border: none;
        border-top: double $secondary;
        border-bottom: double $panel-lighten-2;
    }
    &:hover.size_end {
        border: none;
        border-bottom: double $secondary;
        border-top: double $panel-lighten-2;
    }
    &:focus.size_start {
        border: none;
        border-top: thick $primary;
        border-bottom: double $primary;
    }
    &:focus.size_end {
        border: none;
        border-bottom: thick $primary;
        border-top: double $primary;
    }
    &:focus {
        border: double $primary;
    }
    &.-mime {
        hatch: horizontal white 5%;
    }
}

Default CSS for VerticalEntry widget.

Updated

Bases: BaseMessage[T]

Base message for all entries.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
55
56
57
58
59
60
class Updated(BaseMessage[T]):
    """Base message for all entries."""

    @property
    def entry(self) -> T:
        return self.widget

Resized

Bases: Resized['VerticalEntry']

Entry was resized by the user.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
375
376
class Resized(AbstractEntry.Resized["VerticalEntry"]):
    """Entry was resized by the user."""

Moved

Bases: Moved['VerticalEntry']

Entry was moved by the user.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
378
379
class Moved(AbstractEntry.Moved["VerticalEntry"]):
    """Entry was moved by the user."""

mime classmethod

mime(
    offset: int | None = None,
    size: int | None = None,
    *,
    name: str | None = None,
    markup: bool = True,
    disabled: bool = False,
) -> Self

Instatiate a mime entry. For previewing resizing visually.

PARAMETER DESCRIPTION
offset

Initial offset to use for the entry.

TYPE: int | None DEFAULT: None

size

Initial size to use for the entry.

TYPE: int | None DEFAULT: None

markup

True if markup should be parsed and rendered.

TYPE: bool DEFAULT: True

name

Name of widget.

TYPE: str | None DEFAULT: None

disabled

Whether the static is disabled or not.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Self

Constructed timeline entry.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@classmethod
def mime(
    cls,
    offset: int | None = None,
    size: int | None = None,
    *,
    name: str | None = None,
    markup: bool = True,
    disabled: bool = False,
) -> Self:
    """Instatiate a mime entry. For previewing resizing visually.

    Args:
        offset: Initial offset to use for the entry.
        size: Initial size to use for the entry.
        markup: True if markup should be parsed and rendered.
        name: Name of widget.
        disabled: Whether the static is disabled or not.

    Returns:
        Constructed timeline entry.
    """
    return cls(
        id=f"id-{uuid4()}",
        content="",
        offset=offset,
        size=size,
        markup=markup,
        name=name,
        classes="-mime",
        disabled=disabled,
    )

resize

resize(delta: int, *, tail: bool) -> None

Public method for resizing the widget.

PARAMETER DESCRIPTION
delta

total amount to be moved.

TYPE: int

tail

Whether to adjust the end or the start of the widget.

TYPE: bool

Source code in src/textual_timepiece/timeline/_timeline_entry.py
224
225
226
227
228
229
230
231
232
def resize(self, delta: int, *, tail: bool) -> None:
    """Public method for resizing the widget.

    Args:
        delta: total amount to be moved.
        tail: Whether to adjust the end or the start of the widget.
    """
    delta = self._resize_helper(delta, tail=tail)
    self.post_message(self.Resized(self, self.size, delta))

is_moving

is_moving(offset: Offset) -> bool

Is the widget moving or resizing?

PARAMETER DESCRIPTION
offset

Mouse offset to check against.

TYPE: Offset

RETURNS DESCRIPTION
bool

True if the widget is moving else False.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
287
288
289
290
291
292
293
294
295
296
def is_moving(self, offset: Offset) -> bool:
    """Is the widget moving or resizing?

    Args:
        offset: Mouse offset to check against.

    Returns:
        True if the widget is moving else False.
    """
    return not (self.is_tail(offset) or self.is_head(offset))

VerticalTimelineNavigation

Bases: TimelineNavigation[VerticalTimeline[VerticalEntryType]]

Vertical widget containing a vertical timeline and header.

PARAMETER DESCRIPTION
header

Header to use at the start of the timeline.

TYPE: Widget | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

ATTRIBUTE DESCRIPTION
length

Actual length of the actual timeline navigation.

DEFAULT_CSS

Default CSS for VerticalTimelineNavigation widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
class VerticalTimelineNavigation(
    TimelineNavigation[VerticalTimeline[VerticalEntryType]]
):
    """Vertical widget containing a vertical timeline and header.

    Params:
        header: Header to use at the start of the timeline.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
    """

    Timeline = VerticalTimeline[VerticalEntryT]

    DEFAULT_CSS: ClassVar[str] = """\
    VerticalTimelineNavigation {
        layout: vertical !important;
        height: auto !important;
    }
    """
    """Default CSS for `VerticalTimelineNavigation` widget."""

length class-attribute instance-attribute

length = var[int](96, init=False)

Actual length of the actual timeline navigation.

DEFAULT_CSS class-attribute

VerticalTimelineNavigation {
    layout: vertical !important;
    height: auto !important;
}

Default CSS for VerticalTimelineNavigation widget.

VerticalRuler

Bases: AbstractRuler

Vertical ruler for marking vertical timelines.

PARAMETER DESCRIPTION
duration

Total length of the ruler.

TYPE: int | None DEFAULT: None

marker_factory

Callable for creating the markers.

TYPE: MarkerFactory | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

ATTRIBUTE DESCRIPTION
duration

Total time actual time the ruler spans in seconds.

length

Actual length of the widget.

subdivisions

Amount of subdivisions to use when calculating markers.

time_chunk

Time chunk for each subdivision that the ruler creates.

marker_len

The marker length of the each time_chunk.

DEFAULT_CSS

Default CSS for the VerticalRuler widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
class VerticalRuler(AbstractRuler):
    """Vertical ruler for marking vertical timelines.

    Params:
        duration: Total length of the ruler.
        marker_factory: Callable for creating the markers.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
    """

    DEFAULT_CSS: ClassVar[str] = """\
    VerticalRuler {
        border-left: wide $secondary;
        border-right: wide $secondary;
        height: auto !important;
        width: 8;
    }
    """
    """Default CSS for the `VerticalRuler` widget."""

    def render_line(self, y: int) -> Strip:
        marker_pos, rem = divmod(y, self.marker_len)
        if y and not rem:
            return Strip(
                [
                    Segment(
                        self._factory(marker_pos * self.time_chunk).center(
                            self.size.width
                        ),
                        style=self.get_component_rich_style(
                            "abstractruler--label"
                        ),
                    )
                ]
            )

        return Strip(
            [
                Segment(
                    f" {'─' * (self.size.width - 2)} ",
                    style=self.get_component_rich_style(
                        "abstractruler--empty"
                    ),
                )
            ]
        )

    def get_content_height(
        self,
        container: Size,
        viewport: Size,
        width: int,
    ) -> int:
        return self.length

duration class-attribute instance-attribute

duration = var[int](86400, init=False)

Total time actual time the ruler spans in seconds.

length class-attribute instance-attribute

length = reactive[int](96, layout=True, init=False)

Actual length of the widget.

subdivisions class-attribute instance-attribute

subdivisions = reactive[int](24, init=False)

Amount of subdivisions to use when calculating markers.

Generator gets called this amount of times.

time_chunk class-attribute instance-attribute

time_chunk = Reactive[int](3600, init=False, compute=False)

Time chunk for each subdivision that the ruler creates.

Computed automatically when other reactives change.

marker_len class-attribute instance-attribute

marker_len = Reactive[int](4, init=False, compute=False)

The marker length of the each time_chunk.

DEFAULT_CSS class-attribute

VerticalRuler {
    border-left: wide $secondary;
    border-right: wide $secondary;
    height: auto !important;
    width: 8;
}

Default CSS for the VerticalRuler widget.

RuledVerticalTimeline

Bases: AbstractRuledTimeline[VerticalTimelineNavigation, VerticalRuler]

Ruled vertical timeline with markers.

Note

If providing headers with the header_factory parameter make sure to compensate with top padding for the ruler to keep alignment in place.

PARAMETER DESCRIPTION
total

Total amount of timelines to draw.

TYPE: int | None DEFAULT: None

marker_factory

Factory function for creating markers on the ruler. Defaults to generating markers in the HH:mm format.

TYPE: MarkerFactory | None DEFAULT: None

header_factory

Factory function for creating headers.

TYPE: Callable[[int], Widget] | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

can_maximize

Whether the widget can be maximized or not.

TYPE: bool | None DEFAULT: None

ATTRIBUTE DESCRIPTION
total

Total amount of timelines to compose. Minimum 1.

duration

Total time included in widget in seconds.

length

Actual length of the timelines.

DEFAULT_CSS

Default CSS for RuledVerticalTimeline widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_timeline_manager.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class RuledVerticalTimeline(
    AbstractRuledTimeline[VerticalTimelineNavigation, VerticalRuler]
):
    """Ruled vertical timeline with markers.

    !!! note
        If providing headers with the `header_factory` parameter make
        sure to compensate with top padding for the ruler to keep alignment
        in place.

    Params:
        total: Total amount of timelines to draw.
        marker_factory: Factory function for creating markers on the ruler.
            Defaults to generating markers in the *HH:mm* format.
        header_factory: Factory function for creating headers.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
        can_maximize: Whether the widget can be maximized or not.
    """

    Timeline = VerticalTimelineNavigation[VerticalEntryType]
    DEFAULT_CSS: ClassVar[str] = """\
    RuledVerticalTimeline {
        layout: horizontal;
        width: 1fr;
        & > VerticalTimelineNavigation {
            width: 1fr;
            & > VerticalTimeline {
                width: 1fr;
            }
        }
    }
    """
    """Default CSS for `RuledVerticalTimeline` widget."""

    def compose(self) -> ComposeResult:
        yield VerticalRuler(marker_factory=self._marker_factory).data_bind(
            RuledVerticalTimeline.length
        )
        yield from super().compose()

total class-attribute instance-attribute

total = var[int](1, init=False)

Total amount of timelines to compose. Minimum 1.

duration class-attribute instance-attribute

duration = var[int](86400, init=False)

Total time included in widget in seconds.

length class-attribute instance-attribute

length = reactive[int](96, init=False, layout=True)

Actual length of the timelines.

DEFAULT_CSS class-attribute

RuledVerticalTimeline {
    layout: horizontal;
    width: 1fr;
    & > VerticalTimelineNavigation {
        width: 1fr;
        & > VerticalTimeline {
            width: 1fr;
        }
    }
}

Default CSS for RuledVerticalTimeline widget.

HorizontalTimeline

Bases: AbstractTimeline[HorizontalEntryType]

Timeline that displays entries in a horizontal view.

PARAMETER DESCRIPTION
*children

Entries to intially add to the widget.

TYPE: EntryType DEFAULT: ()

duration

Size of the widget.

TYPE: int | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

tile

Whether to tile the timeline or not. Set to False if a single row of entries is preferred.

TYPE: bool DEFAULT: True

CLASS DESCRIPTION
Updated

Base class for all timeline messages.

Created

Sent when a new entry is created.

Deleted

Sent when an entry is deleted.

Selected

Sent when a new entry selected.

METHOD DESCRIPTION
action_delete_entry

Remove the selected or provided entry from the timeline.

action_adjust_tail

Adjust the tail of the selected timeline entry.

action_adjust_head

Adjust the head of the selected timeline entry.

ATTRIBUTE DESCRIPTION
BINDINGS

All bindings for AbstractTimeline widget.

TYPE: list[BindingType]

COMPONENT_CLASSES

All component classes for the AbstractTimeline widget.

TYPE: set[str]

length

Actual size of the widget with the direction size of the widget.

markers

Custom markers to place on the timeline.

tile

Is calendar tiling enabled?

TYPE: bool

selected

Currently highlighted entry. None if there is nothing selected.

TYPE: EntryType | None

DEFAULT_CSS

Default CSS for HorizontalTimeline widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
class HorizontalTimeline(AbstractTimeline[HorizontalEntryType]):
    """Timeline that displays entries in a horizontal view.

    Params:
        *children: Entries to intially add to the widget.
        duration: Size of the widget.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
        tile: Whether to tile the timeline or not. Set to `False` if a single
            row of entries is preferred.
    """

    Entry = HorizontalEntry  # type: ignore[assignment] # FIX: Need to research to how to correctly accomplish this.
    Layout = HorizontalTimelineLayout
    DURATION: ClassVar[str] = "width"
    DEFAULT_CSS: ClassVar[str] = """\
    HorizontalTimeline {
        width: auto !important;
        height: 28;
        border-top: tall $secondary;
        border-bottom: tall $secondary;
        &:hover {
            border-top: thick $secondary;
            border-bottom: thick $secondary;
        }
        &:focus {
            border-top: outer $primary;
            border-bottom: outer $primary;
        }
    }
    """
    """Default CSS for `HorizontalTimeline` widget."""

    class Created(
        AbstractTimeline.Created[HorizontalEntryT, HorizontalTimelineType]
    ):
        """Sent when a new entry is created."""

    class Deleted(
        AbstractTimeline.Deleted[HorizontalEntryT, HorizontalTimelineType]
    ):
        """Sent when an entry is deleted."""

    class Selected(
        AbstractTimeline.Selected[HorizontalEntryT, HorizontalTimelineType]
    ):
        """Sent when a new entry selected."""

    def _create_strip(self) -> Strip:
        """Prerenders the strip for reuse on each line."""
        defaults = (self.get_component_rich_style("timeline--normal"), "")

        segs = list[Segment]()
        add_seg = segs.append
        prev_style = None
        current_strip = ""
        for x in range(self.size.width):
            style, _ = self.markers.get(x, defaults)
            if prev_style and style != prev_style:
                add_seg(Segment(current_strip, prev_style))
                current_strip = ""

            prev_style = style
            current_strip += "│"

        add_seg(Segment(current_strip, prev_style))

        return Strip(segs, self.size.width).simplify()

    def render_lines(self, crop: Region) -> list[Strip]:
        self._cached_strip = self._create_strip()
        return super().render_lines(crop)

    def render_line(self, y: int) -> Strip:
        return self._cached_strip

    def _watch_markers(
        self,
        old: AbstractTimeline.Markers,
        new: AbstractTimeline.Markers,
    ) -> None:
        if old.keys() ^ new.keys():
            self.refresh()

    def _calc_entry_size(self, end: Offset) -> tuple[int, int]:
        start = cast("Offset", self._start)
        return start.x if start.x < end.x else end.x, abs(end.x - start.x)

    def pre_layout(self, layout: HorizontalTimelineLayout) -> None:  # type: ignore[override]
        # Sorting with the public sort method will cause infinite layout calls.
        self._nodes._sort(  # TODO: This needs a public solution .
            key=lambda w: (w.offset.x, cast("Scalar", w.styles.width).value),
        )

    def get_content_width(self, container: Size, viewport: Size) -> int:
        return self.length

BINDINGS class-attribute

BINDINGS: list[BindingType] = [
    Binding(
        "ctrl+down,ctrl+right",
        "adjust_tail",
        tooltip="Move entry to the backward.",
    ),
    Binding(
        "ctrl+up,ctrl+left",
        "adjust_head",
        tooltip="Move entry to the forward.",
    ),
    Binding(
        "alt+shift+down,alt+shift+left",
        "adjust_tail(True, True)",
        tooltip="Resize the tail end of the entry.",
    ),
    Binding(
        "alt+shift+up,alt+shift+right",
        "adjust_head(True, True)",
        tooltip="Resize the end of the entry forward.",
    ),
    Binding(
        "shift+up,shift+left",
        "adjust_head(False, True)",
        tooltip="Resize the start of the entry backward.",
    ),
    Binding(
        "shift+down,shift+right",
        "adjust_tail(False, True)",
        tooltip="Move the head of the entry forward.",
    ),
    Binding(
        "ctrl+d,delete,backspace",
        "delete_entry",
        "Delete Entry",
        tooltip="Delete the selected entry.",
    ),
    Binding(
        "escape",
        "clear_active",
        "Clear",
        priority=True,
        show=False,
        tooltip="Cancel creating an entry or deselect entries.",
    ),
]

All bindings for AbstractTimeline widget.

Key(s) Description
ctrl+down,ctrl+right Move entry to the backward.
ctrl+up,ctrl+left Move entry to the forward.
alt+shift+down,alt+shift+left Resize the tail end of the entry.
alt+shift+up,alt+shift+right Resize the end of the entry forward.
shift+up,shift+left Resize the start of the entry backward.
shift+down,shift+right Move the head of the entry forward.
ctrl+d,delete,backspace Delete the selected entry.
escape Cancel creating an entry or deselect entries.

COMPONENT_CLASSES class-attribute

COMPONENT_CLASSES: set[str] = {'timeline--normal'}

All component classes for the AbstractTimeline widget.

Class Description
timeline--normal Target all lines without set markers.

length class-attribute instance-attribute

length = Reactive[int](96, init=False, layout=True)

Actual size of the widget with the direction size of the widget.

markers class-attribute instance-attribute

markers = Reactive[Markers](
    MappingProxyType({}), init=False, compute=False, repaint=False
)

Custom markers to place on the timeline.

tile property writable

tile: bool

Is calendar tiling enabled?

selected property

selected: EntryType | None

Currently highlighted entry. None if there is nothing selected.

DEFAULT_CSS class-attribute

HorizontalTimeline {
    width: auto !important;
    height: 28;
    border-top: tall $secondary;
    border-bottom: tall $secondary;
    &:hover {
        border-top: thick $secondary;
        border-bottom: thick $secondary;
    }
    &:focus {
        border-top: outer $primary;
        border-bottom: outer $primary;
    }
}

Default CSS for HorizontalTimeline widget.

Updated

Bases: BaseMessage[TimelineType], Generic[EntryT, TimelineType]

Base class for all timeline messages.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
81
82
83
84
85
86
87
88
89
90
91
92
93
class Updated(BaseMessage[TimelineType], Generic[EntryT, TimelineType]):
    """Base class for all timeline messages."""

    def __init__(self, widget: TimelineType, entry: EntryT) -> None:
        super().__init__(widget)

        self.entry = entry
        """Entry that was updated."""

    @property
    def timeline(self) -> TimelineType:
        """Alias for `widget` attribute."""
        return self.widget
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Created

Bases: Created[HorizontalEntryT, HorizontalTimelineType]

Sent when a new entry is created.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
585
586
587
588
class Created(
    AbstractTimeline.Created[HorizontalEntryT, HorizontalTimelineType]
):
    """Sent when a new entry is created."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Deleted

Bases: Deleted[HorizontalEntryT, HorizontalTimelineType]

Sent when an entry is deleted.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
590
591
592
593
class Deleted(
    AbstractTimeline.Deleted[HorizontalEntryT, HorizontalTimelineType]
):
    """Sent when an entry is deleted."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

Selected

Bases: Selected[HorizontalEntryT, HorizontalTimelineType]

Sent when a new entry selected.

ATTRIBUTE DESCRIPTION
entry

Entry that was updated.

timeline

Alias for widget attribute.

TYPE: TimelineType

Source code in src/textual_timepiece/timeline/_base_timeline.py
595
596
597
598
class Selected(
    AbstractTimeline.Selected[HorizontalEntryT, HorizontalTimelineType]
):
    """Sent when a new entry selected."""
entry instance-attribute
entry = entry

Entry that was updated.

timeline property
timeline: TimelineType

Alias for widget attribute.

action_delete_entry

action_delete_entry(id: str | None = None) -> None

Remove the selected or provided entry from the timeline.

PARAMETER DESCRIPTION
id

If removing an un-highlighted widget.

TYPE: str | None DEFAULT: None

Source code in src/textual_timepiece/timeline/_base_timeline.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def action_delete_entry(self, id: str | None = None) -> None:
    """Remove the selected or provided entry from the timeline.

    Args:
        id: If removing an un-highlighted widget.
    """
    if id:
        try:
            entry = self.query_one(f"#{id}", self.Entry)
        except NoMatches:
            return
    elif self.selected:
        entry = self.selected
    else:
        return

    entry.remove()
    self.post_message(self.Deleted(self, entry))

action_adjust_tail

action_adjust_tail(tail: bool = False, resize: bool = False) -> None

Adjust the tail of the selected timeline entry.

PARAMETER DESCRIPTION
tail

Increase the size if resizing.

TYPE: bool DEFAULT: False

resize

Resize the entry instead of moving.

TYPE: bool DEFAULT: False

Source code in src/textual_timepiece/timeline/_base_timeline.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
def action_adjust_tail(
    self,
    tail: bool = False,
    resize: bool = False,
) -> None:
    """Adjust the tail of the selected timeline entry.

    Args:
        tail: Increase the size if resizing.
        resize: Resize the entry instead of moving.
    """
    if resize:
        cast("EntryType", self.selected).resize(1, tail=tail)
    else:
        cast("EntryType", self.selected).move(1)

action_adjust_head

action_adjust_head(tail: bool = False, resize: bool = False) -> None

Adjust the head of the selected timeline entry.

PARAMETER DESCRIPTION
tail

Increase the size if resizing.

TYPE: bool DEFAULT: False

resize

Resize the entry instead of moving.

TYPE: bool DEFAULT: False

Source code in src/textual_timepiece/timeline/_base_timeline.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def action_adjust_head(
    self,
    tail: bool = False,
    resize: bool = False,
) -> None:
    """Adjust the head of the selected timeline entry.

    Args:
        tail: Increase the size if resizing.
        resize: Resize the entry instead of moving.
    """
    if resize:
        cast("EntryType", self.selected).resize(-1, tail=not tail)
    else:
        cast("EntryType", self.selected).move(-1)

HorizontalEntry

Bases: AbstractEntry

Horizontal entry for a horizontal timeline layout.

PARAMETER DESCRIPTION
id

ID of the Widget.

TYPE: str

content

A Rich renderable, or string containing console markup.

TYPE: RenderableType | SupportsVisual DEFAULT: ''

offset

Initial offset to use for the entry.

TYPE: int | None DEFAULT: None

size

Initial size to use for the entry.

TYPE: int | None DEFAULT: None

markup

True if markup should be parsed and rendered.

TYPE: bool DEFAULT: True

name

Name of widget.

TYPE: str | None DEFAULT: None

classes

Space separated list of class names.

TYPE: str | None DEFAULT: None

disabled

Whether the static is disabled or not.

TYPE: bool DEFAULT: False

CLASS DESCRIPTION
Updated

Base message for all entries.

Resized

Entry was resized by the user.

Moved

Entry was moved by the user.

METHOD DESCRIPTION
mime

Instatiate a mime entry. For previewing resizing visually.

resize

Public method for resizing the widget.

is_moving

Is the widget moving or resizing?

ATTRIBUTE DESCRIPTION
clicked

Initial point where the entry was clicked when moving or resizing.

dimension

Alias for the size of the entry depending on orientation.

TYPE: int

DEFAULT_CSS

Default CSS for HorizontalEntry widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_timeline_entry.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
class HorizontalEntry(AbstractEntry):
    """Horizontal entry for a horizontal timeline layout.

    Params:
        id: ID of the Widget.
        content: A Rich renderable, or string containing console markup.
        offset: Initial offset to use for the entry.
        size: Initial size to use for the entry.
        markup: True if markup should be parsed and rendered.
        name: Name of widget.
        classes: Space separated list of class names.
        disabled: Whether the static is disabled or not.
    """

    class Resized(AbstractEntry.Resized["HorizontalEntry"]):
        """Entry was resized by the user."""

    class Moved(AbstractEntry.Moved["HorizontalEntry"]):
        """Entry was moved by the user."""

    DIMENSION = "width"
    EDGE_MARGIN: ClassVar[int] = 2
    DEFAULT_CSS: ClassVar[str] = """\
    HorizontalEntry {
        min-width: 2;
        width: 4;
        height: 100%;
        border-left: double $panel-lighten-2;
        border-right: double $panel-lighten-2;
        &:hover.size_start {
            border: none;
            border-left: double $secondary;
            border-right: double $panel-lighten-2;
        }
        &:hover.size_end {
            border: none;
            border-right: double $secondary;
            border-left: double $panel-lighten-2;
        }
        &:focus.size_start {
            border: none;
            border-left: thick $primary;
            border-right: double $primary;
        }
        &:focus.size_end {
            border: none;
            border-left: double $primary;
            border-right: thick $primary;
        }
        &:focus {
            border: double $primary;
        }
        &.-mime {
            hatch: vertical white 5%;
        }
    }
    """
    """Default CSS for `HorizontalEntry` widget."""

    def sel_delta(self, event: MouseEvent) -> int:
        return event.delta_x

    def is_tail(self, offset: Offset) -> bool:
        return 0 <= offset.x <= self.EDGE_MARGIN

    def is_head(self, offset: Offset) -> bool:
        width = cast("Scalar", self.styles.width).value
        return width - self.EDGE_MARGIN <= offset.x <= width

    def is_start(self, offset: Offset) -> bool:
        return offset.x < cast("Scalar", self.styles.width).value / 2

    def move(self, delta: int) -> None:
        offset = Offset(delta, 0)
        self.offset = self.offset + offset
        self.post_message(self.Moved(self, delta))

    def merge(self, other: AbstractEntry) -> AwaitRemove:
        x1, x2 = self.offset.x, other.offset.x
        start = x1 if x1 <= x2 else x2
        self.offset = Offset(start, 0)
        e1, e2 = self.end, other.end
        self.styles.width = abs(start - (e1 if e1 >= e2 else e2))
        return other.remove()

    def _resize_helper(self, delta: int, *, tail: bool) -> int:
        if tail:
            delta *= -1

        new_width = cast("Scalar", self.styles.width).value + delta

        if tail and new_width != cast("Scalar", self.styles.width).value:
            self.offset += Offset(-delta, 0)

        self.styles.width = new_width
        return delta

    def set_dims(
        self, offset: int | None = None, size: int | None = None
    ) -> None:
        if offset:
            self.offset = Offset(offset, 0)
        if size:
            self.styles.width = size

    @property
    def start(self) -> int:
        return self.offset.x

clicked class-attribute instance-attribute

clicked = var[Offset | None](None, init=False)

Initial point where the entry was clicked when moving or resizing.

dimension property writable

dimension: int

Alias for the size of the entry depending on orientation.

DEFAULT_CSS class-attribute

HorizontalEntry {
    min-width: 2;
    width: 4;
    height: 100%;
    border-left: double $panel-lighten-2;
    border-right: double $panel-lighten-2;
    &:hover.size_start {
        border: none;
        border-left: double $secondary;
        border-right: double $panel-lighten-2;
    }
    &:hover.size_end {
        border: none;
        border-right: double $secondary;
        border-left: double $panel-lighten-2;
    }
    &:focus.size_start {
        border: none;
        border-left: thick $primary;
        border-right: double $primary;
    }
    &:focus.size_end {
        border: none;
        border-left: double $primary;
        border-right: thick $primary;
    }
    &:focus {
        border: double $primary;
    }
    &.-mime {
        hatch: vertical white 5%;
    }
}

Default CSS for HorizontalEntry widget.

Updated

Bases: BaseMessage[T]

Base message for all entries.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
55
56
57
58
59
60
class Updated(BaseMessage[T]):
    """Base message for all entries."""

    @property
    def entry(self) -> T:
        return self.widget

Resized

Bases: Resized['HorizontalEntry']

Entry was resized by the user.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
488
489
class Resized(AbstractEntry.Resized["HorizontalEntry"]):
    """Entry was resized by the user."""

Moved

Bases: Moved['HorizontalEntry']

Entry was moved by the user.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
491
492
class Moved(AbstractEntry.Moved["HorizontalEntry"]):
    """Entry was moved by the user."""

mime classmethod

mime(
    offset: int | None = None,
    size: int | None = None,
    *,
    name: str | None = None,
    markup: bool = True,
    disabled: bool = False,
) -> Self

Instatiate a mime entry. For previewing resizing visually.

PARAMETER DESCRIPTION
offset

Initial offset to use for the entry.

TYPE: int | None DEFAULT: None

size

Initial size to use for the entry.

TYPE: int | None DEFAULT: None

markup

True if markup should be parsed and rendered.

TYPE: bool DEFAULT: True

name

Name of widget.

TYPE: str | None DEFAULT: None

disabled

Whether the static is disabled or not.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Self

Constructed timeline entry.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@classmethod
def mime(
    cls,
    offset: int | None = None,
    size: int | None = None,
    *,
    name: str | None = None,
    markup: bool = True,
    disabled: bool = False,
) -> Self:
    """Instatiate a mime entry. For previewing resizing visually.

    Args:
        offset: Initial offset to use for the entry.
        size: Initial size to use for the entry.
        markup: True if markup should be parsed and rendered.
        name: Name of widget.
        disabled: Whether the static is disabled or not.

    Returns:
        Constructed timeline entry.
    """
    return cls(
        id=f"id-{uuid4()}",
        content="",
        offset=offset,
        size=size,
        markup=markup,
        name=name,
        classes="-mime",
        disabled=disabled,
    )

resize

resize(delta: int, *, tail: bool) -> None

Public method for resizing the widget.

PARAMETER DESCRIPTION
delta

total amount to be moved.

TYPE: int

tail

Whether to adjust the end or the start of the widget.

TYPE: bool

Source code in src/textual_timepiece/timeline/_timeline_entry.py
224
225
226
227
228
229
230
231
232
def resize(self, delta: int, *, tail: bool) -> None:
    """Public method for resizing the widget.

    Args:
        delta: total amount to be moved.
        tail: Whether to adjust the end or the start of the widget.
    """
    delta = self._resize_helper(delta, tail=tail)
    self.post_message(self.Resized(self, self.size, delta))

is_moving

is_moving(offset: Offset) -> bool

Is the widget moving or resizing?

PARAMETER DESCRIPTION
offset

Mouse offset to check against.

TYPE: Offset

RETURNS DESCRIPTION
bool

True if the widget is moving else False.

Source code in src/textual_timepiece/timeline/_timeline_entry.py
287
288
289
290
291
292
293
294
295
296
def is_moving(self, offset: Offset) -> bool:
    """Is the widget moving or resizing?

    Args:
        offset: Mouse offset to check against.

    Returns:
        True if the widget is moving else False.
    """
    return not (self.is_tail(offset) or self.is_head(offset))

HorizontalTimelineNavigation

Bases: TimelineNavigation[HorizontalTimeline[HorizontalEntryType]]

Horizontal widget containing a horizontal timeline and header.

PARAMETER DESCRIPTION
header

Header to use at the start of the timeline.

TYPE: Widget | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

ATTRIBUTE DESCRIPTION
length

Actual length of the actual timeline navigation.

DEFAULT_CSS

Default CSS for HorizontalTimelineNavigation widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
class HorizontalTimelineNavigation(
    TimelineNavigation[HorizontalTimeline[HorizontalEntryType]]
):
    """Horizontal widget containing a horizontal timeline and header.

    Params:
        header: Header to use at the start of the timeline.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
    """

    Timeline = HorizontalTimeline[HorizontalEntryT]

    DEFAULT_CSS: ClassVar[str] = """\
    HorizontalTimelineNavigation {
        layout: horizontal !important;
        width: auto !important;
    }
    """
    """Default CSS for `HorizontalTimelineNavigation` widget."""

length class-attribute instance-attribute

length = var[int](96, init=False)

Actual length of the actual timeline navigation.

DEFAULT_CSS class-attribute

HorizontalTimelineNavigation {
    layout: horizontal !important;
    width: auto !important;
}

Default CSS for HorizontalTimelineNavigation widget.

HorizontalRuler

Bases: AbstractRuler

Horizontal ruler for marking horizontal timelines.

PARAMETER DESCRIPTION
duration

Total length of the ruler.

TYPE: int | None DEFAULT: None

marker_factory

Callable for creating the markers.

TYPE: MarkerFactory | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

ATTRIBUTE DESCRIPTION
duration

Total time actual time the ruler spans in seconds.

length

Actual length of the widget.

subdivisions

Amount of subdivisions to use when calculating markers.

time_chunk

Time chunk for each subdivision that the ruler creates.

marker_len

The marker length of the each time_chunk.

DEFAULT_CSS

Default CSS for HorizontalRuler widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_base_timeline.py
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
class HorizontalRuler(AbstractRuler):
    """Horizontal ruler for marking horizontal timelines.

    Params:
        duration: Total length of the ruler.
        marker_factory: Callable for creating the markers.
        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
    """

    DEFAULT_CSS: ClassVar[str] = """\
    HorizontalRuler {
        border-top: tall $secondary;
        border-bottom: tall $secondary;
        width: auto !important;
        height: 3;
        hatch: vertical white 5%;
    }
    """
    """Default CSS for `HorizontalRuler` widget."""

    def render_line(self, y: int) -> Strip:
        if y != (self.size.height // 2):
            return Strip.blank(self.size.width)

        style = self.get_component_rich_style("abstractruler--label")
        return Strip(
            [
                Segment(self._factory(t).rjust(self.marker_len), style)
                for t in range(self.time_chunk, self.duration, self.time_chunk)
            ]
        ).simplify()

    def get_content_width(self, container: Size, viewport: Size) -> int:
        return self.length

duration class-attribute instance-attribute

duration = var[int](86400, init=False)

Total time actual time the ruler spans in seconds.

length class-attribute instance-attribute

length = reactive[int](96, layout=True, init=False)

Actual length of the widget.

subdivisions class-attribute instance-attribute

subdivisions = reactive[int](24, init=False)

Amount of subdivisions to use when calculating markers.

Generator gets called this amount of times.

time_chunk class-attribute instance-attribute

time_chunk = Reactive[int](3600, init=False, compute=False)

Time chunk for each subdivision that the ruler creates.

Computed automatically when other reactives change.

marker_len class-attribute instance-attribute

marker_len = Reactive[int](4, init=False, compute=False)

The marker length of the each time_chunk.

DEFAULT_CSS class-attribute

HorizontalRuler {
    border-top: tall $secondary;
    border-bottom: tall $secondary;
    width: auto !important;
    height: 3;
    hatch: vertical white 5%;
}

Default CSS for HorizontalRuler widget.

RuledHorizontalTimeline

Bases: AbstractRuledTimeline[HorizontalTimelineNavigation, HorizontalRuler]

Ruled horizontal timeline with markers.

Note

If providing headers with the header_factory parameter make sure to compensate with left padding for the ruler to keep alignment in place.

PARAMETER DESCRIPTION
total

Total amount of timelines to draw.

TYPE: int | None DEFAULT: None

marker_factory

Factory function for creating markers on the ruler. Defaults to generating markers in the HH:mm format.

TYPE: MarkerFactory | None DEFAULT: None

header_factory

Factory function for creating headers.

TYPE: Callable[[int], Widget] | None DEFAULT: None

name

The name of the widget.

TYPE: str | None DEFAULT: None

id

The ID of the widget in the DOM.

TYPE: str | None DEFAULT: None

classes

The CSS classes for the widget.

TYPE: str | None DEFAULT: None

disabled

Whether the widget is disabled or not.

TYPE: bool DEFAULT: False

can_maximize

Whether the widget can be maximized or not.

TYPE: bool | None DEFAULT: None

ATTRIBUTE DESCRIPTION
total

Total amount of timelines to compose. Minimum 1.

duration

Total time included in widget in seconds.

DEFAULT_CSS

Default CSS for RuledHorizontalTimeline widget.

TYPE: str

Source code in src/textual_timepiece/timeline/_timeline_manager.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
class RuledHorizontalTimeline(
    AbstractRuledTimeline[HorizontalTimelineNavigation, HorizontalRuler]
):
    """Ruled horizontal timeline with markers.

    !!! note
        If providing headers with the `header_factory` parameter make
        sure to compensate with left padding for the ruler to keep alignment
        in place.

    Params:
        total: Total amount of timelines to draw.
        marker_factory: Factory function for creating markers on the ruler.
            Defaults to generating markers in the *HH:mm* format.
        header_factory: Factory function for creating headers.

        name: The name of the widget.
        id: The ID of the widget in the DOM.
        classes: The CSS classes for the widget.
        disabled: Whether the widget is disabled or not.
        can_maximize: Whether the widget can be maximized or not.
    """

    Timeline = HorizontalTimelineNavigation[HorizontalEntryType]
    DEFAULT_CSS: ClassVar[str] = """\
    RuledHorizontalTimeline {
        height: 1fr;
        & > HorizontalTimelineNavigation {
            height: 1fr;
            & > HorizontalTimeline {
                height: 1fr;
            }
        }
    }
    """
    """Default CSS for `RuledHorizontalTimeline` widget."""

    def __init__(
        self,
        total: int | None = None,
        marker_factory: AbstractRuler.MarkerFactory | None = None,
        header_factory: Callable[[int], Widget] | None = None,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        *,
        disabled: bool = False,
        can_maximize: bool | None = None,
    ) -> None:
        super().__init__(
            total,
            marker_factory,
            header_factory,
            name,
            id,
            classes,
            disabled=disabled,
            can_maximize=can_maximize,
        )
        self.length = 192

    def compose(self) -> ComposeResult:
        yield HorizontalRuler(marker_factory=self._marker_factory).data_bind(
            RuledHorizontalTimeline.length
        )
        yield from super().compose()

total class-attribute instance-attribute

total = var[int](1, init=False)

Total amount of timelines to compose. Minimum 1.

duration class-attribute instance-attribute

duration = var[int](86400, init=False)

Total time included in widget in seconds.

DEFAULT_CSS class-attribute

RuledHorizontalTimeline {
    height: 1fr;
    & > HorizontalTimelineNavigation {
        height: 1fr;
        & > HorizontalTimeline {
            height: 1fr;
        }
    }
}

Default CSS for RuledHorizontalTimeline widget.