Skip to content

main

The main screen.

Attributes⚓︎

CHAR_OFFSET module-attribute ⚓︎

CHAR_OFFSET = math.floor(0.4 * MAX_CHARS)

Offset to keep the next characters visible.

MAX_CHARS module-attribute ⚓︎

MAX_CHARS = math.floor(0.8 * get_terminal_size()[0])

Determine maximum characters that can fit in 80% of the terminal width.

Classes⚓︎

Main ⚓︎

Bases: Screen

The main screen for the application.

Source code in tui_typer_tutor/screens/main.py
class Main(Screen):  # type: ignore[type-arg]
    """The main screen for the application."""

    DEFAULT_CSS: ClassVar[str] = """
    Screen {
        background: #1b2b34;
    }

    #left-pad {
        width: 10%;
    }
    #content {
        width: 80%;
        align: center middle;
    }

    .tutor-container {
        content-align-horizontal: left;
        height: 2;
    }
    #text-container {
        color: #7e8993;
    }
    #typed-container .error {
        color: #ec5f67;
    }
    #typed-container .success {
        color: #99c794;
    }
    #typed-unknown {
        color: #b69855;
    }
    """

    BINDINGS: ClassVar[list[Binding]] = [  # type: ignore[assignment]
        Binding('ctrl+q', 'save_and_quit', 'Save and Quit'),
    ]

    keys: Keys
    metrics: SessionMetrics
    width: int = 0

    def action_save_and_quit(self) -> None:
        """Save and quit."""
        append_csv(self.metrics.end_session(self.keys))
        # TODO: Print out or display a success message on completion!
        sys.exit(0)

    def compose(self) -> ComposeResult:  # noqa: PLR6301
        """Layout."""
        yield Header()
        with Horizontal():
            yield Vertical(id='left-pad')
            # FYI: ^^ couldn't get 'center' alignment to work
            with Vertical(id='content'):
                yield Horizontal(id='text-container', classes='tutor-container')
                yield Horizontal(id='typed-container', classes='tutor-container')
                yield Label(id='typed-unknown', classes='warning')
        yield Footer()

    def on_mount(self) -> None:
        """On widget mount."""
        # TODO: Support more customization
        seed_file = get_config().seed_file
        self.keys = Keys(expected=load_seed_data(seed_text=seed_file.read_text()))
        self.metrics = SessionMetrics.from_filename(filename=seed_file.name)
        cont = self.query_one('#text-container', Horizontal)
        for key in self.keys.expected:  # FYI: Mounts all expected keys and crops
            cont.mount(Label(key.text, classes='text'))

    @beartype
    def on_key(self, event: Key) -> None:  # noqa: CAC001
        """Capture all key presses and show in the typed input."""
        if event.key in {'ctrl+q', 'ctrl+backslash'}:
            return  # ignore bound keys

        try:
            on_keypress(event.key, self.keys)
        except AtEndOfExpectedError:
            self.action_save_and_quit()

        if self.keys.last_was_delete:
            if self.width:
                self.width -= 1
            with suppress(NoMatches):
                self.query('Label.typed').last().remove()
                self.query_one('#typed-unknown', Label).update('')
        else:
            self.width += 1
            cursor_width = MAX_CHARS - CHAR_OFFSET
            if self.width >= cursor_width:
                self.query('Label.typed').first().remove()
                self.query('Label.text').first().remove()
            # Choose the class
            color_class = 'success'
            display_text = self.keys.typed[-1].text
            if not self.keys.typed[-1].was_correct:
                color_class = 'error'
                # Ensure that invisible characters are displayed
                display_text = display_text.strip() or '█'
            typed_label = Label(display_text, classes=f'typed {color_class}')
            self.query_one('#typed-container', Horizontal).mount(typed_label)
            textual_unknown = event.key if display_text == UNKNOWN else ''
            self.query_one('#typed-unknown', Label).update(textual_unknown)

Functions⚓︎

action_save_and_quit ⚓︎
action_save_and_quit()

Save and quit.

Source code in tui_typer_tutor/screens/main.py
def action_save_and_quit(self) -> None:
    """Save and quit."""
    append_csv(self.metrics.end_session(self.keys))
    # TODO: Print out or display a success message on completion!
    sys.exit(0)
compose ⚓︎
compose()

Layout.

Source code in tui_typer_tutor/screens/main.py
def compose(self) -> ComposeResult:  # noqa: PLR6301
    """Layout."""
    yield Header()
    with Horizontal():
        yield Vertical(id='left-pad')
        # FYI: ^^ couldn't get 'center' alignment to work
        with Vertical(id='content'):
            yield Horizontal(id='text-container', classes='tutor-container')
            yield Horizontal(id='typed-container', classes='tutor-container')
            yield Label(id='typed-unknown', classes='warning')
    yield Footer()
on_key ⚓︎
on_key(event)

Capture all key presses and show in the typed input.

Source code in tui_typer_tutor/screens/main.py
@beartype
def on_key(self, event: Key) -> None:  # noqa: CAC001
    """Capture all key presses and show in the typed input."""
    if event.key in {'ctrl+q', 'ctrl+backslash'}:
        return  # ignore bound keys

    try:
        on_keypress(event.key, self.keys)
    except AtEndOfExpectedError:
        self.action_save_and_quit()

    if self.keys.last_was_delete:
        if self.width:
            self.width -= 1
        with suppress(NoMatches):
            self.query('Label.typed').last().remove()
            self.query_one('#typed-unknown', Label).update('')
    else:
        self.width += 1
        cursor_width = MAX_CHARS - CHAR_OFFSET
        if self.width >= cursor_width:
            self.query('Label.typed').first().remove()
            self.query('Label.text').first().remove()
        # Choose the class
        color_class = 'success'
        display_text = self.keys.typed[-1].text
        if not self.keys.typed[-1].was_correct:
            color_class = 'error'
            # Ensure that invisible characters are displayed
            display_text = display_text.strip() or '█'
        typed_label = Label(display_text, classes=f'typed {color_class}')
        self.query_one('#typed-container', Horizontal).mount(typed_label)
        textual_unknown = event.key if display_text == UNKNOWN else ''
        self.query_one('#typed-unknown', Label).update(textual_unknown)
on_mount ⚓︎
on_mount()

On widget mount.

Source code in tui_typer_tutor/screens/main.py
def on_mount(self) -> None:
    """On widget mount."""
    # TODO: Support more customization
    seed_file = get_config().seed_file
    self.keys = Keys(expected=load_seed_data(seed_text=seed_file.read_text()))
    self.metrics = SessionMetrics.from_filename(filename=seed_file.name)
    cont = self.query_one('#text-container', Horizontal)
    for key in self.keys.expected:  # FYI: Mounts all expected keys and crops
        cont.mount(Label(key.text, classes='text'))

Functions⚓︎