Skip to content

Layout

This module contains all the layout logic.

  • We define a QuizLayoutBase abstract base class.
  • We define a QuizLayout base class.

FancyQuizLayout (QuizLayoutBase) dataclass

FancyQuizLayout(layout: Optional[rich.layout.Layout] = None)

Source code in quizli/layout.py
@dataclass
class FancyQuizLayout(QuizLayoutBase):
    layout: Optional[Layout] = None

    def print(self):
        print(self.layout)

    def initialize(self, quiz_name: str, n_questions: int) -> None:
        self.quiz_name = quiz_name
        self.n_questions = n_questions

        if self.layout is None:
            self.layout = self._make_quiz_layout()
        self.statsbar = self._make_statsbar(n_questions)
        self.progressbar = self._make_progressbar(n_questions)

        self.layout["stats"].update(Panel(self.statsbar, title="Stats"))
        self.layout["progress"].update(Panel(self.progressbar))
        self._reset_dialog()

        print(self.layout)

    def show_question(self, item: QuizItem, idx: int) -> None:
        self._reset_answer()
        self._reset_dialog()
        self.layout["question"].update(
            Panel(
                Align.center(f"[bold]{item.question}", vertical="middle"),
                title=f"[blue]Question {idx}/{self.n_questions}[/blue] | {self.quiz_name}",
                border_style="green",
                highlight=True,
            )
        )
        print(self.layout)

    def reveal_answer(self, item: QuizItem, answer_correct: bool) -> None:
        color = "bold green" if answer_correct else "bold red"
        self.layout["answer"].update(
            Panel(
                Align.center(f"[{color}]{item.answer}", vertical="middle"),
                title=f"[blue]Answer",
                border_style="green",
                highlight=True,
            )
        )
        print(self.layout)

        if answer_correct:
            self.layout["dialog"].update(
                Panel(
                    Align.center(
                        f":fire: [{color}]That's correct! :fire: [/{color}] {motivate()}",
                        vertical="middle",
                    ),
                )
            )
            # Advance 'correct' statsbar
            self.statsbar.advance(0)
        else:
            self.layout["dialog"].update(
                Panel(
                    Align.center(
                        f"[red bold]:cross_mark: That's wrong :cross_mark: [/red bold]",
                        vertical="middle",
                    ),
                )
            )
            # Advance 'wrong' statsbar
            self.statsbar.advance(1)

        self.progressbar.advance(0)
        self.print()

    def show_results(self, has_won: Optional[bool] = None) -> None:
        if has_won is None:
            result_message = f"[blue bold]See you soon ![/blue bold]"
        elif has_won:
            result_message = f":trophy: [green bold]You won![/green bold] :trophy: See you soon, champion!"
        else:
            result_message = (
                f"[red bold]Game Over![/red bold] :disappointed_face: See you soon !"
            )

        n_correct = self.statsbar.tasks[0].completed
        n_false = self.statsbar.tasks[1].completed
        n_total = self.progressbar.tasks[0].total

        result_message += f"\nYou have answered {em(n_correct, 'green')} question(s) correctly and {em(n_false, 'red')} incorrectly"
        l = Layout(
            Panel(
                Align.center(result_message, vertical="middle"),
                title=f"[blue]Results",
            ),
        )
        print(l)

    def _reset_answer(self) -> None:
        self.layout["answer"].update(
            Panel(
                Align.center(
                    f":question-emoji::question-emoji::question-emoji:",
                    vertical="middle",
                ),
                title=f"[blue]Answer",
            )
        )

    def _reset_dialog(self) -> None:
        self.layout["dialog"].update(
            Panel(
                Align.center(
                    f"[bold blue]Type in your answer!",
                    vertical="middle",
                ),
            )
        )

    @staticmethod
    def _make_quiz_layout() -> Layout:
        layout = Layout(name="root")
        layout.split_column(
            Layout(name="progress"),
            Layout(name="question"),
            Layout(name="answer"),
            Layout(name="footer"),
        )
        layout["progress"].ratio = 2
        layout["question"].ratio = 8
        layout["answer"].ratio = 8
        layout["footer"].ratio = 3

        layout["footer"].split_row(Layout(name="dialog"), Layout(name="stats"))
        layout["dialog"].ratio = 2
        layout["stats"].ratio = 1
        return layout

    @staticmethod
    def _make_progressbar(total: int) -> Progress:
        progressbar = Progress(
            "{task.description}",
            BarColumn(),
            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
            expand=True,
        )
        progressbar.add_task("[cyan]Quiz Progress", total=total)
        return progressbar

    @staticmethod
    def _make_statsbar(total: int) -> Progress:
        statsbar = Progress(
            "{task.description}",
            SpinnerColumn(),
            BarColumn(),
            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
        )
        statsbar.add_task("[green]Correct", total=total)
        statsbar.add_task("[red]Wrong", total=total)
        return statsbar

initialize(self, quiz_name, n_questions)

Initializes the quiz layout

Parameters:

Name Type Description Default
quiz_name str

Name of the quiz

required
n_questions int

Number of questions in the quiz

required
Source code in quizli/layout.py
def initialize(self, quiz_name: str, n_questions: int) -> None:
    self.quiz_name = quiz_name
    self.n_questions = n_questions

    if self.layout is None:
        self.layout = self._make_quiz_layout()
    self.statsbar = self._make_statsbar(n_questions)
    self.progressbar = self._make_progressbar(n_questions)

    self.layout["stats"].update(Panel(self.statsbar, title="Stats"))
    self.layout["progress"].update(Panel(self.progressbar))
    self._reset_dialog()

    print(self.layout)

reveal_answer(self, item, answer_correct)

Update layout to show answer

Parameters:

Name Type Description Default
item QuizItem

The quiz item that was asked

required
answer_correct bool

Whether the question was answered correctly

required
Source code in quizli/layout.py
def reveal_answer(self, item: QuizItem, answer_correct: bool) -> None:
    color = "bold green" if answer_correct else "bold red"
    self.layout["answer"].update(
        Panel(
            Align.center(f"[{color}]{item.answer}", vertical="middle"),
            title=f"[blue]Answer",
            border_style="green",
            highlight=True,
        )
    )
    print(self.layout)

    if answer_correct:
        self.layout["dialog"].update(
            Panel(
                Align.center(
                    f":fire: [{color}]That's correct! :fire: [/{color}] {motivate()}",
                    vertical="middle",
                ),
            )
        )
        # Advance 'correct' statsbar
        self.statsbar.advance(0)
    else:
        self.layout["dialog"].update(
            Panel(
                Align.center(
                    f"[red bold]:cross_mark: That's wrong :cross_mark: [/red bold]",
                    vertical="middle",
                ),
            )
        )
        # Advance 'wrong' statsbar
        self.statsbar.advance(1)

    self.progressbar.advance(0)
    self.print()

show_question(self, item, idx)

Update layout to show the question

Parameters:

Name Type Description Default
item QuizItem

The quiz item to be asked

required
idx int

The index of the quiz item in the quiz

required
Source code in quizli/layout.py
def show_question(self, item: QuizItem, idx: int) -> None:
    self._reset_answer()
    self._reset_dialog()
    self.layout["question"].update(
        Panel(
            Align.center(f"[bold]{item.question}", vertical="middle"),
            title=f"[blue]Question {idx}/{self.n_questions}[/blue] | {self.quiz_name}",
            border_style="green",
            highlight=True,
        )
    )
    print(self.layout)

show_results(self, has_won=None)

Update the layout to show the quiz result

Parameters:

Name Type Description Default
has_won bool

Whether the user has won

None
Source code in quizli/layout.py
def show_results(self, has_won: Optional[bool] = None) -> None:
    if has_won is None:
        result_message = f"[blue bold]See you soon ![/blue bold]"
    elif has_won:
        result_message = f":trophy: [green bold]You won![/green bold] :trophy: See you soon, champion!"
    else:
        result_message = (
            f"[red bold]Game Over![/red bold] :disappointed_face: See you soon !"
        )

    n_correct = self.statsbar.tasks[0].completed
    n_false = self.statsbar.tasks[1].completed
    n_total = self.progressbar.tasks[0].total

    result_message += f"\nYou have answered {em(n_correct, 'green')} question(s) correctly and {em(n_false, 'red')} incorrectly"
    l = Layout(
        Panel(
            Align.center(result_message, vertical="middle"),
            title=f"[blue]Results",
        ),
    )
    print(l)

QuizLayoutBase (ABC)

This abstract base class serves as blueprint for QuizLayout classes

Source code in quizli/layout.py
class QuizLayoutBase(ABC):
    """This abstract base class serves as blueprint for `QuizLayout` classes"""

    @abstractmethod
    def initialize(self, quiz_name: str, n_questions: int) -> None:
        """Initializes the quiz layout

        Args:
            quiz_name (str): Name of the quiz
            n_questions (int): Number of questions in the quiz
        """
        pass

    @abstractmethod
    def show_question(self, item: QuizItem, idx: int) -> None:
        """Update layout to show the question

        Args:
            item (QuizItem): The quiz item to be asked
            idx (int): The index of the quiz item in the quiz
        """
        pass

    @abstractmethod
    def reveal_answer(self, item: QuizItem, answer_correct: bool) -> None:
        """Update layout to show answer

        Args:
            item (QuizItem): The quiz item that was asked
            answer_correct (bool): Whether the question was answered correctly
        """
        pass

    def show_results(self, has_won: bool) -> None:
        """Update the layout to show the quiz result

        Args:
            has_won (bool): Whether the user has won
        """
        pass

initialize(self, quiz_name, n_questions)

Initializes the quiz layout

Parameters:

Name Type Description Default
quiz_name str

Name of the quiz

required
n_questions int

Number of questions in the quiz

required
Source code in quizli/layout.py
@abstractmethod
def initialize(self, quiz_name: str, n_questions: int) -> None:
    """Initializes the quiz layout

    Args:
        quiz_name (str): Name of the quiz
        n_questions (int): Number of questions in the quiz
    """
    pass

reveal_answer(self, item, answer_correct)

Update layout to show answer

Parameters:

Name Type Description Default
item QuizItem

The quiz item that was asked

required
answer_correct bool

Whether the question was answered correctly

required
Source code in quizli/layout.py
@abstractmethod
def reveal_answer(self, item: QuizItem, answer_correct: bool) -> None:
    """Update layout to show answer

    Args:
        item (QuizItem): The quiz item that was asked
        answer_correct (bool): Whether the question was answered correctly
    """
    pass

show_question(self, item, idx)

Update layout to show the question

Parameters:

Name Type Description Default
item QuizItem

The quiz item to be asked

required
idx int

The index of the quiz item in the quiz

required
Source code in quizli/layout.py
@abstractmethod
def show_question(self, item: QuizItem, idx: int) -> None:
    """Update layout to show the question

    Args:
        item (QuizItem): The quiz item to be asked
        idx (int): The index of the quiz item in the quiz
    """
    pass

show_results(self, has_won)

Update the layout to show the quiz result

Parameters:

Name Type Description Default
has_won bool

Whether the user has won

required
Source code in quizli/layout.py
def show_results(self, has_won: bool) -> None:
    """Update the layout to show the quiz result

    Args:
        has_won (bool): Whether the user has won
    """
    pass

Last update: March 11, 2022