diff --git a/rio/components/app_root.py b/rio/components/app_root.py index 1549efb5..2925d55f 100644 --- a/rio/components/app_root.py +++ b/rio/components/app_root.py @@ -70,12 +70,12 @@ class Sidebar(component.Component): rio.Text( self.session.app.name, style="heading2", - justify='left', + justify="left", ), rio.Text( "TODO: Subtext", style="dim", - justify='left', + justify="left", ), align_y=0, ), diff --git a/rio/components/labeled_column.py b/rio/components/labeled_column.py index 575f7e10..2a6b4369 100644 --- a/rio/components/labeled_column.py +++ b/rio/components/labeled_column.py @@ -102,7 +102,7 @@ class LabeledColumn(Component): def build(self) -> Component: rows = [ [ - rio.Text(label, justify='right'), + rio.Text(label, justify="right"), child, ] for label, child in self.content.items() diff --git a/rio/components/list_items.py b/rio/components/list_items.py index 62e017da..3b5e5c86 100644 --- a/rio/components/list_items.py +++ b/rio/components/list_items.py @@ -195,7 +195,7 @@ class SimpleListItem(Component): text_children = [ rio.Text( self.text, - justify='left', + justify="left", ) ] @@ -205,7 +205,7 @@ class SimpleListItem(Component): self.secondary_text, wrap=True, style="dim", - justify='left', + justify="left", ) ) diff --git a/rio/debug/client_side_debugger/component_details.py b/rio/debug/client_side_debugger/component_details.py index 0d88a90b..b2ab9aee 100644 --- a/rio/debug/client_side_debugger/component_details.py +++ b/rio/debug/client_side_debugger/component_details.py @@ -106,7 +106,7 @@ class ComponentDetails(rio.Component): rio.Text( text, style="dim" if is_label else "text", - justify='right' if is_label else 'left', + justify="right" if is_label else "left", ), row_index, column_index, @@ -126,7 +126,7 @@ class ComponentDetails(rio.Component): rio.Text( target.key, style="dim", - justify='left', + justify="left", ), ] @@ -160,7 +160,7 @@ class ComponentDetails(rio.Component): rio.Text( f"{file} line {line}", style="dim", - justify='left', + justify="left", ), row_index, 0, @@ -227,8 +227,8 @@ class ComponentDetails(rio.Component): row_index += 1 # Header - result.add(rio.Text("width", style="dim", justify='left'), row_index, 1) - result.add(rio.Text("height", style="dim", justify='left'), row_index, 2) + result.add(rio.Text("width", style="dim", justify="left"), row_index, 1) + result.add(rio.Text("height", style="dim", justify="left"), row_index, 2) row_index += 1 # The size as specified in Python diff --git a/rio/debug/client_side_debugger/deploy_page.py b/rio/debug/client_side_debugger/deploy_page.py index ad2b2079..6d5c414e 100644 --- a/rio/debug/client_side_debugger/deploy_page.py +++ b/rio/debug/client_side_debugger/deploy_page.py @@ -8,7 +8,7 @@ class DeployPage(rio.Component): "Deploy", style="heading2", margin=1, - justify='left', + justify="left", ), rio.Column( rio.Icon( diff --git a/rio/debug/client_side_debugger/docs_page.py b/rio/debug/client_side_debugger/docs_page.py index 3c7db26e..4dc42371 100644 --- a/rio/debug/client_side_debugger/docs_page.py +++ b/rio/debug/client_side_debugger/docs_page.py @@ -8,7 +8,7 @@ class DocsPage(rio.Component): "Documentation", style="heading2", margin=1, - justify='left', + justify="left", ), rio.Column( rio.Text( diff --git a/rio/debug/client_side_debugger/icons_page.py b/rio/debug/client_side_debugger/icons_page.py index 45c14321..c0f53ff8 100644 --- a/rio/debug/client_side_debugger/icons_page.py +++ b/rio/debug/client_side_debugger/icons_page.py @@ -162,9 +162,11 @@ class IconsPage(rio.Component): async def _on_select_icon_by_name(self, icon_name: str) -> None: # Parse the icon name - icon_set, icon_name, icon_variant = ( - rio.icon_registry.IconRegistry.parse_icon_name(icon_name) - ) + ( + icon_set, + icon_name, + icon_variant, + ) = rio.icon_registry.IconRegistry.parse_icon_name(icon_name) # Find all available variants for ( @@ -210,7 +212,7 @@ class IconsPage(rio.Component): rio.Text( "Configure", style="heading3", - justify='left', + justify="left", ) ) @@ -285,7 +287,7 @@ class IconsPage(rio.Component): rio.Text( "Example Code", style="heading3", - justify='left', + justify="left", margin_top=1, ) ) diff --git a/rio/debug/client_side_debugger/project_page.py b/rio/debug/client_side_debugger/project_page.py index c1ae6a6f..52d13911 100644 --- a/rio/debug/client_side_debugger/project_page.py +++ b/rio/debug/client_side_debugger/project_page.py @@ -36,13 +36,13 @@ class ProjectPage(rio.Component): rio.Text( project_name, style="heading2", - justify='left', + justify="left", ), rio.Text( str(self.project.project_directory), style="dim", margin_bottom=2, - justify='left', + justify="left", ), rio.Text( "To launch your project, Rio needs to know the name of your python module and in which variable you've stored your app. You can configure those here.", diff --git a/rio/debug/client_side_debugger/theme_picker_page.py b/rio/debug/client_side_debugger/theme_picker_page.py index 4cff9338..31333998 100644 --- a/rio/debug/client_side_debugger/theme_picker_page.py +++ b/rio/debug/client_side_debugger/theme_picker_page.py @@ -201,7 +201,7 @@ class PalettePicker(rio.Component): # fill=palette.foreground, ), selectable=False, - justify='left', + justify="left", ), rio.Text( f"#{palette.background.hex}", @@ -209,7 +209,7 @@ class PalettePicker(rio.Component): # font_size=1, fill=palette.foreground.replace(opacity=0.5), ), - justify='left', + justify="left", ), spacing=0.2, margin_x=1, @@ -419,7 +419,7 @@ class ThemePickerPage(rio.Component): style="heading3", margin_top=1, margin_bottom=1, - justify='left', + justify="left", ), radius_sliders, # Theme Variants @@ -428,7 +428,7 @@ class ThemePickerPage(rio.Component): style="heading3", margin_top=1, margin_bottom=1, - justify='left', + justify="left", ), rio.Row( rio.Spacer(), @@ -451,7 +451,7 @@ class ThemePickerPage(rio.Component): style="heading3", margin_top=1, # margin_bottom=1, Not used for now, since markdown has an oddly large margin anyway - justify='left', + justify="left", ), rio.Markdown( f""" diff --git a/rio/snippets/snippet-files/old-tutorial-biography/__init__.py b/rio/snippets/snippet-files/old-tutorial-biography/__init__.py deleted file mode 100644 index e56c4eb7..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import rio - -from . import pages - -app = rio.App( - name="Biography", - pages=[ - rio.Page("", pages.BiographyPage), - ], -) - - -fastapi_app = app.as_fastapi() diff --git a/rio/snippets/snippet-files/old-tutorial-biography/assets/jane-doe.jpg b/rio/snippets/snippet-files/old-tutorial-biography/assets/jane-doe.jpg deleted file mode 100644 index aec4e550..00000000 Binary files a/rio/snippets/snippet-files/old-tutorial-biography/assets/jane-doe.jpg and /dev/null differ diff --git a/rio/snippets/snippet-files/old-tutorial-biography/assets/john-doe.jpg b/rio/snippets/snippet-files/old-tutorial-biography/assets/john-doe.jpg deleted file mode 100644 index d38610ad..00000000 Binary files a/rio/snippets/snippet-files/old-tutorial-biography/assets/john-doe.jpg and /dev/null differ diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/__init__.py b/rio/snippets/snippet-files/old-tutorial-biography/components/__init__.py deleted file mode 100644 index 2b535bcb..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .about_me import AboutMe -from .contact import Contact -from .history import History -from .projects import Projects -from .skill_bars import SkillBars diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/about_me.py b/rio/snippets/snippet-files/old-tutorial-biography/components/about_me.py deleted file mode 100644 index b6bbbb86..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/about_me.py +++ /dev/null @@ -1,41 +0,0 @@ -from pathlib import Path -from typing import * # type: ignore - -import rio - - -class AboutMe(rio.Component): - def build(self) -> rio.Component: - return rio.Row( - rio.Image( - Path("me.jpg"), - width=11, - height=11, - corner_radius=99999, - fill_mode="zoom", - margin_right=2, - ), - rio.Column( - rio.Text( - "Jane Doe", - style="heading1", - justify='left', - ), - rio.Text( - "Data Analyst", - justify='left', - ), - rio.Text( - "AI Researcher", - justify='left', - ), - rio.Text( - "Python Developer", - justify='left', - ), - spacing=0.5, - align_y=0, - width="grow", - ), - width="grow", - ) diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/contact.py b/rio/snippets/snippet-files/old-tutorial-biography/components/contact.py deleted file mode 100644 index 8ca0c109..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/contact.py +++ /dev/null @@ -1,50 +0,0 @@ -from dataclasses import KW_ONLY -from typing import * # type: ignore - -import rio - - -class Contact(rio.Component): - text: str - icon_name_or_url: str | rio.URL - _: KW_ONLY - link: rio.URL | None = None - - def build(self) -> rio.Component: - # Prepare the image. This varies depending on whether an icon or an - # image should be displayed. - if isinstance(self.icon_name_or_url, str): - image = rio.Icon( - self.icon_name_or_url, - width=2, - height=2, - margin_x=0.5, - ) - else: - image = rio.Image( - self.icon_name_or_url, - width=2, - height=2, - margin_x=0.5, - ) - - # Combine everything - result = rio.Row( - image, - rio.Text( - self.text, - justify='left', - width="grow", - ), - width="grow", - margin=0.2, - ) - - # If a link is provided, wrap the result in a link - if self.link is not None: - result = rio.Link( - result, - self.link, - ) - - return result diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/history.py b/rio/snippets/snippet-files/old-tutorial-biography/components/history.py deleted file mode 100644 index a6c5233e..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/history.py +++ /dev/null @@ -1,114 +0,0 @@ -from typing import * # type: ignore - -import rio - - -class HistoryItem(rio.Component): - from_string: str - to_string: str - title: str - organization: str - details: str - icon: str - - is_open: bool = False - - def on_press(self) -> None: - self.is_open = not self.is_open - - def build(self) -> rio.Component: - return rio.Card( - rio.Row( - rio.Icon( - self.icon, - width=3, - height=3, - margin_right=2, - align_x=0, - align_y=0, - ), - rio.Column( - rio.Text( - f"{self.from_string} - {self.to_string}", - justify='left', - style=rio.TextStyle(fill=rio.Color.GREY), - ), - rio.Text( - f"{self.title} at {self.organization}", - style="heading3", - justify='left', - ), - rio.Revealer( - None, - rio.Text( - self.details, - wrap=True, - justify='left', - ), - is_open=self.is_open, - ), - spacing=0.3, - width="grow", - align_y=0, - ), - ), - on_press=self.on_press, - ) - - -class History(rio.Component): - def build(self) -> rio.Component: - return rio.Column( - rio.Text( - "Professional Experience", - style="heading2", - margin_top=3, - ), - HistoryItem( - "2021", - "Present", - "AI Researcher", - "DataWorks Inc.", - "Developed and deployed machine learning models to improve the " - "accuracy of the company's recommender system. Collaborated " - "with a team of 10 researchers to implement a novel algorithm " - "for predicting user behavior.", - "science", - ), - HistoryItem( - "2018", - "2021", - "Data Analyst", - "Insight Technologies", - "Conducted in-depth data analysis and implemented machine " - "learning algorithms to improve predictive models, resulting " - "in a 15% increase in accuracy. Collaborated with " - "cross-functional teams to develop and deploy scalable AI " - "solutions for optimizing business operations.", - "monitoring", - ), - rio.Text( - "Education", - style="heading2", - margin_top=3, - ), - HistoryItem( - "2015", - "2018", - "Master in Data Science", - "Carnegie Mellon University, Pittsburgh, PA", - 'Thesis: "Predictive Modeling in Financial Markets using ' - 'Machine Learning Techniques"', - "school", - ), - HistoryItem( - "2012", - "2015", - "Bachelor in Software Engineering", - "University of Texas, Austin, TX", - 'Thesis: "Design and Implementation of an Intelligent Traffic ' - 'Control System"', - "school", - ), - spacing=2, - ) diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/projects.py b/rio/snippets/snippet-files/old-tutorial-biography/components/projects.py deleted file mode 100644 index d67e89c6..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/projects.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import * # type: ignore - -import rio - - -class Project(rio.Component): - name: str - details: str - link: rio.URL - - def on_press(self) -> None: - self.session.navigate_to(self.link) - - def build(self) -> rio.Component: - return rio.Card( - rio.Column( - rio.Text( - self.name, - style="heading3", - ), - rio.Text( - self.details, - wrap=True, - ), - spacing=0.5, - ), - on_press=self.on_press, - ) - - -class Projects(rio.Component): - def build(self) -> rio.Component: - return rio.Grid( - ( - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - ), - ( - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - Project( - "uniserde", - "Pure Python, easy to use Serializer", - rio.URL("https://gitlab.com/Vivern/uniserde"), - ), - ), - row_spacing=2, - column_spacing=2, - ) diff --git a/rio/snippets/snippet-files/old-tutorial-biography/components/skill_bars.py b/rio/snippets/snippet-files/old-tutorial-biography/components/skill_bars.py deleted file mode 100644 index 5dcc2e36..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/components/skill_bars.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import * # type: ignore - -import rio - - -class SkillBars(rio.Component): - skills: dict[str, int] - - def build(self) -> rio.Component: - rows = [] - - for name, level in self.skills.items(): - rows.append( - rio.Column( - rio.Text( - name, - justify='left', - ), - rio.ProgressBar( - level / 10, - ), - ) - ) - - return rio.Card( - rio.Column( - *rows, - spacing=0.5, - ), - ) diff --git a/rio/snippets/snippet-files/old-tutorial-biography/pages/__init__.py b/rio/snippets/snippet-files/old-tutorial-biography/pages/__init__.py deleted file mode 100644 index f575379d..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/pages/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .biography_page import BiographyPage diff --git a/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page.py b/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page.py deleted file mode 100644 index 9305a143..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page.py +++ /dev/null @@ -1,125 +0,0 @@ -from typing import * # type: ignore - -import rio - -from .. import components as comps - - -class BiographyPage(rio.Component): - def build(self) -> rio.Component: - thm = self.session.theme - - return rio.Column( - rio.Rectangle( - fill=rio.LinearGradientFill( - (thm.primary_palette.background, 0), - (thm.primary_palette.background.brighter(0.2), 1), - ), - height=1.0, - ), - rio.Column( - comps.AboutMe(), - rio.Grid( - ( - comps.Contact( - "123 Main Street, Anytown, USA 12345", - "location-on", - ), - comps.Contact( - "Gitlab", - rio.URL( - "https://about.gitlab.com/images/press/logo/svg/gitlab-logo-500.svg" - ), - link=rio.URL("https://gitlab.com"), - ), - ), - ( - comps.Contact( - "(123) 456-7890", - "call:fill", - ), - comps.Contact( - "GitHub", - rio.URL( - "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" - ), - link=rio.URL("https://github.com"), - ), - ), - ( - comps.Contact( - "janedoe@email.com", - "mail", - link=rio.URL("mailto:janedoe@email.com"), - ), - comps.Contact( - "LinkedIn", - rio.URL( - "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/LinkedIn_logo_initials.png/768px-LinkedIn_logo_initials.png" - ), - link=rio.URL("https://linkedin.com"), - ), - ), - row_spacing=1.5, - column_spacing=5, - width="grow", - margin_y=2, - ), - rio.Row( - comps.SkillBars( - { - "Python": 5, - "Rust": 3, - "SQL": 4, - "R": 3, - }, - width="grow", - align_y=0, - ), - comps.SkillBars( - { - "Pandas": 5, - "Numpy": 5, - "SciPy": 3, - "Matplotlib": 4, - "Seaborn": 4, - }, - width="grow", - align_y=0, - ), - comps.SkillBars( - { - "English": 5, - "Spanish": 3, - "French": 2, - }, - width="grow", - align_y=0, - ), - width="grow", - spacing=5, - ), - comps.History(margin_x=10), - rio.Text( - "Personal Projects", - style="heading2", - margin_x=8, - margin_top=3, - ), - comps.Projects(), - rio.Overlay( - rio.IconButton( - icon="material/mail:fill", - margin_right=3, - margin_bottom=3, - align_x=1, - align_y=1, - ) - ), - margin_y=4, - spacing=2, - width=40, - align_x=0.5, - ), - align_y=0, - ) diff --git a/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page_after_tutorial_4.py b/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page_after_tutorial_4.py deleted file mode 100644 index 21fb342a..00000000 --- a/rio/snippets/snippet-files/old-tutorial-biography/pages/biography_page_after_tutorial_4.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import * # type: ignore - -import rio - -from .. import components as comps - - -class BiographyPage(rio.Component): - def build(self) -> rio.Component: - return comps.AboutMe() diff --git a/rio/snippets/snippet-files/project-template-Empty/components/__init__.py b/rio/snippets/snippet-files/project-template-Empty/components/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/__init__.py b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/__init__.py index e69de29b..847f1bbe 100644 --- a/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/__init__.py +++ b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/__init__.py @@ -0,0 +1 @@ +from .field import Field as Field diff --git a/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/field.py b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/field.py new file mode 100644 index 00000000..86825cde --- /dev/null +++ b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/field.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + + +# +class Field(rio.Component): + """ + This component represents a single field in the Tic Tac Toe game. It can + contain an X, an O, or be empty. If the field is empty, the player can press + on it to set their respective mark. + """ + + # The current value of the field. + value: Literal["X", "O", ""] + + # If this is `True`, the field will dim its content. This allows the game to + # dim any fields that are not part of the winning combination when a player + # has won. + dim: bool + + # The game needs to know when a player presses on a field, so it can update + # the board accordingly. This custom event handler serves that purpose. + on_press: rio.EventHandler[[]] = None + + def build(self) -> rio.Component: + # We'll need to specify the size of several components. This variable + # makes it easier to adjust the size of the fields. + field_size = 3 + + # If the field is empty, allow the player to press on it. Since buttons + # would look out of place here, cards are a nice alternative. + if self.value == "": + return rio.Card( + content=rio.Spacer( + width=field_size, + height=field_size, + ), + on_press=self.on_press, + ) + + # For fields that already contain an X or O, show the respective + # icon. + # + # Vary the color based on the player + color = rio.Color.RED if self.value == "X" else rio.Color.BLUE + + # If a player has won, and this field isn't part of the winning + # combination, dim it. + if self.dim: + color = color.replace(opacity=0.2) + + # Create the icon to represent the field + return rio.Icon( + "material/close" if self.value == "X" else "material/circle", + fill=color, + width=field_size, + height=field_size, + ) + + +# diff --git a/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/pages/tic_tac_toe_page.py b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/pages/tic_tac_toe_page.py index 356fd69e..fe6fe14f 100644 --- a/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/pages/tic_tac_toe_page.py +++ b/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/pages/tic_tac_toe_page.py @@ -2,11 +2,12 @@ from __future__ import annotations # import functools -from dataclasses import field from typing import * # type: ignore import rio +from .. import components as comps + # @@ -21,8 +22,12 @@ class TicTacToePage(rio.Component): Everything is contained right in this class. """ - # The contents of all fields - fields: list[Literal["X", "O", ""]] = field(default_factory=lambda: [""] * 9) + # The contents of all fields. Each field can contain an X, an O, or be + # empty. The initial state is an empty board. + # + # The first value is the top-left field. The remaining fields follow from + # left to right, top to bottom. + fields: list[Literal["X", "O", ""]] = [""] * 9 # The player who is currently on turn player: Literal["X", "O"] = "X" @@ -32,7 +37,7 @@ class TicTacToePage(rio.Component): # If there is a winner, these are the indices of the fields which made them # win - winning_indices: set[int] = field(default_factory=set) + winning_indices: set[int] = set() def find_winner(self) -> None: """ @@ -104,41 +109,15 @@ class TicTacToePage(rio.Component): def build(self) -> rio.Component: # Spawn components for the fields field_components: list[rio.Component] = [] - field_size = 3 for index, field in enumerate(self.fields): - # Allow the player to press on empty fields. Since buttons would - # look out of place here, cards are a nice alternative. - if field == "": - field_components.append( - rio.Card( - content=rio.Spacer( - width=field_size, - height=field_size, - ), - on_press=functools.partial(self.on_press, index), - ) - ) - - # For fields that already contain an X or O, show the respective - # icon - else: - # Vary the color based on the player - color = rio.Color.RED if field == "X" else rio.Color.BLUE - - # If a player has won highlight the winning fields - if self.winner in ("X", "O") and index not in self.winning_indices: - color = color.replace(opacity=0.3) - - # Create the icon to represent the field - field_components.append( - rio.Icon( - "material/close" if field == "X" else "material/circle", - fill=color, - width=field_size, - height=field_size, - ) + field_components.append( + comps.Field( + value=field, + dim=self.winner is not None and index not in self.winning_indices, + on_press=functools.partial(self.on_press, index), ) + ) # Come up with a status message if self.winner in ("X", "O"): diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/tic_tac_toe_page.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/tic_tac_toe_page.py new file mode 100644 index 00000000..c2d60f3c --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/tic_tac_toe_page.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + + +# +class TicTacToePage(rio.Component): + # The contents of all fields. Each field can contain an X, an O, or be + # empty. The initial state is an empty board. + fields: list[Literal["X", "O", ""]] = [""] * 9 + + def build(self) -> rio.Component: + # Spawn components for the fields + field_components: list[rio.Component] = [] + + for index, field in enumerate(self.fields): + field_components.append(rio.Text(f"Field {index}")) + + # Arrange all components in a grid + return rio.Grid( + field_components[0:3], + field_components[3:6], + field_components[6:9], + row_spacing=1, + column_spacing=1, + align_x=0.5, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/field.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/field.py new file mode 100644 index 00000000..7e583912 --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/field.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + + +# +class Field(rio.Component): + value: Literal["X", "O", ""] + + on_press: rio.EventHandler[[]] = None + + def build(self) -> rio.Component: + # If the field is empty, allow the player to press on it. Since buttons + # would look out of place here, cards are a nice alternative. + if self.value == "": + return rio.Card( + content=rio.Spacer( + width=3, + height=3, + ), + on_press=self.on_press, + ) + + # For fields that already contain an X or O, show the respective icon. + # Also vary the color based on the player. + if self.value == "X": + color = rio.Color.RED + icon = "material/close" + else: + color = rio.Color.BLUE + icon = "material/circle" + + return rio.Icon( + icon=icon, + fill=color, + width=3, + height=3, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/tic_tac_toe_page.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/tic_tac_toe_page.py new file mode 100644 index 00000000..afbf8a36 --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/tic_tac_toe_page.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + +from . import field as comps + + +# +class TicTacToePage(rio.Component): + # The contents of all fields. Each field can contain an X, an O, or be + # empty. The initial state is an empty board. + fields: list[Literal["X", "O", ""]] = [""] * 9 + + def build(self) -> rio.Component: + # Spawn components for the fields + field_components: list[rio.Component] = [] + + for index, field in enumerate(self.fields): + comps.Field( + value=field, + ) + + # Arrange all components in a grid + return rio.Grid( + field_components[0:3], + field_components[3:6], + field_components[6:9], + row_spacing=1, + column_spacing=1, + align_x=0.5, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/field.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/field.py new file mode 100644 index 00000000..f8089d8b --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/field.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + + +# +class Field(rio.Component): + value: Literal["X", "O", ""] + + on_press: rio.EventHandler[[]] = None + + def build(self) -> rio.Component: + # If the field is empty, allow the player to press on it. Since buttons + # would look out of place here, cards are a nice alternative. + if self.value == "": + return rio.Card( + content=rio.Spacer( + width=3, + height=3, + ), + on_press=self.on_press, + ) + + # For fields that already contain an X or O, show the respective + # icon. + # + # Vary the color based on the player + color = rio.Color.RED if self.value == "X" else rio.Color.BLUE + + return rio.Icon( + "material/close" if self.value == "X" else "material/circle", + fill=color, + width=3, + height=3, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/tic_tac_toe_page.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/tic_tac_toe_page.py new file mode 100644 index 00000000..d98e4fda --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/tic_tac_toe_page.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +import functools +from typing import * # type: ignore + +import rio + +from . import field as comps + + +# +class TicTacToePage(rio.Component): + # The contents of all fields. Each field can contain an X, an O, or be + # empty. The initial state is an empty board. + fields: list[Literal["X", "O", ""]] = [""] * 9 + + # The player who is currently on turn + player: Literal["X", "O"] = "X" + + async def on_press(self, index: int) -> None: + """ + This function reacts to presses on the fields, and updates the game + state accordingly. + """ + + # Set the field on that index + self.fields[index] = self.player + + # Next player + self.player = "X" if self.player == "O" else "O" + + def on_reset(self) -> None: + """ + Reset the game to its initial state. + """ + self.fields = [""] * 9 + self.player = "X" + + def build(self) -> rio.Component: + # Spawn components for the fields + field_components: list[rio.Component] = [] + + for index, field in enumerate(self.fields): + field_components.append( + comps.Field( + value=field, + on_press=functools.partial(self.on_press, index), + ) + ) + + # Come up with a status message + message = f"{self.player}'s turn" + + # Arrange all components in a grid + return rio.Column( + rio.Text(message, style="heading1"), + rio.Grid( + field_components[0:3], + field_components[3:6], + field_components[6:9], + row_spacing=1, + column_spacing=1, + align_x=0.5, + ), + rio.Button( + "Reset", + icon="material/refresh", + style="plain", + on_press=self.on_reset, + ), + spacing=2, + margin=2, + align_x=0.5, + align_y=0.0, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/field.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/field.py new file mode 100644 index 00000000..a6a16a80 --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/field.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import * # type: ignore + +import rio + + +# +class Field(rio.Component): + value: Literal["X", "O", ""] + dim: bool + + on_press: rio.EventHandler[[]] = None + + def build(self) -> rio.Component: + # If the field is empty, allow the player to press on it. Since buttons + # would look out of place here, cards are a nice alternative. + if self.value == "": + return rio.Card( + content=rio.Spacer( + width=3, + height=3, + ), + on_press=self.on_press, + ) + + # For fields that already contain an X or O, show the respective + # icon. + # + # Vary the color based on the player + color = rio.Color.RED if self.value == "X" else rio.Color.BLUE + + # If a player has won, and this field isn't part of the winning + # combination, dim it. + if self.dim: + color = color.replace(opacity=0.2) + + return rio.Icon( + "material/close" if self.value == "X" else "material/circle", + fill=color, + width=3, + height=3, + ) + + +# diff --git a/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/tic_tac_toe_page.py b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/tic_tac_toe_page.py new file mode 100644 index 00000000..ff9ab0b4 --- /dev/null +++ b/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/tic_tac_toe_page.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +import functools +from typing import * # type: ignore + +import rio + +from . import field as comps + + +# +class TicTacToePage(rio.Component): + # The contents of all fields. Each field can contain an X, an O, or be + # empty. The initial state is an empty board. + fields: list[Literal["X", "O", ""]] = [""] * 9 + + # The player who is currently on turn + player: Literal["X", "O"] = "X" + + # The winner of the game, if any + winner: Literal["X", "O", "draw"] | None = None + + # If there is a winner, these are the indices of the fields which made them + # win + winning_indices: set[int] = set() + + def find_winner(self) -> None: + """ + Look if there is a winner on the board, and stores it in the component's + state. Also updates the winning indices accordingly. + """ + # Create a list of all possible winning combinations. If all of these + # indices have the same value, then that value is the winner. + winning_combinations = [ + [0, 1, 2], # Top row + [3, 4, 5], # Middle row + [6, 7, 8], # Bottom row + [0, 3, 6], # Left column + [1, 4, 7], # Middle column + [2, 5, 8], # Right column + [0, 4, 8], # Top-left to bottom-right diagonal + [2, 4, 6], # Top-right to bottom-left diagonal + ] + + # Look for winners + for combination in winning_combinations: + values = [self.fields[i] for i in combination] + if values.count("X") == 3: + self.winner = "X" + self.winning_indices = set(combination) + return + + if values.count("O") == 3: + self.winner = "O" + self.winning_indices = set(combination) + return + + # If no fields are empty, it's a draw + if "" not in self.fields: + self.winner = "draw" + return + + # There is no winner yet + pass + + async def on_press(self, index: int) -> None: + """ + This function reacts to presses on the fields, and updates the game + state accordingly. + """ + + # If the game is already over, don't do anything + if self.winner: + return + + # Set the field on that index + self.fields[index] = self.player + + # Next player + self.player = "X" if self.player == "O" else "O" + + # See if there is a winner + self.find_winner() + + def on_reset(self) -> None: + """ + Reset the game to its initial state. + """ + self.fields = [""] * 9 + self.player = "X" + self.winner = None + self.winning_indices = set() + + def build(self) -> rio.Component: + # Spawn components for the fields + field_components: list[rio.Component] = [] + + for index, field in enumerate(self.fields): + field_components.append( + comps.Field( + value=field, + dim=self.winner is not None and index not in self.winning_indices, + on_press=functools.partial(self.on_press, index), + ) + ) + + # Come up with a status message + if self.winner in ("X", "O"): + message = f"{self.winner} has won!" + elif self.winner == "draw": + message = "It's a draw!" + else: + message = f"{self.player}'s turn" + + # Arrange all components in a grid + return rio.Column( + rio.Text(message, style="heading1"), + rio.Grid( + field_components[0:3], + field_components[3:6], + field_components[6:9], + row_spacing=1, + column_spacing=1, + align_x=0.5, + ), + rio.Button( + "Reset", + icon="material/refresh", + style="plain", + on_press=self.on_reset, + ), + spacing=2, + margin=2, + align_x=0.5, + align_y=0.0, + ) + + +#