Skip to content

Commit bbc557f

Browse files
committed
Add declarative tic-tac-toe example and improve component typing
Added a new declarative tic-tac-toe example app in Python. Improved the @component decorator in flet by using ParamSpec and TypeVar for better type safety and argument handling, and preserved function metadata with functools.wraps.
1 parent 2bd09b0 commit bbc557f

2 files changed

Lines changed: 115 additions & 4 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import flet as ft
2+
3+
4+
@ft.component
5+
def Square(value: str, on_click):
6+
return ft.Button(
7+
ft.Icon(ft.Icons.CIRCLE_OUTLINED)
8+
if value == "O"
9+
else ft.Icon(ft.Icons.CLOSE)
10+
if value == "X"
11+
else "",
12+
width=50,
13+
height=50,
14+
style=ft.ButtonStyle(shape=ft.RoundedRectangleBorder(radius=5), padding=0),
15+
on_click=on_click,
16+
)
17+
18+
19+
@ft.component
20+
def Board(x_is_next: bool, squares: list[str], on_play):
21+
def handle_click(i: int):
22+
if squares[i] or calculate_winner(squares):
23+
return
24+
next_squares = squares[:]
25+
next_squares[i] = "X" if x_is_next else "O"
26+
on_play(next_squares)
27+
28+
winner = calculate_winner(squares)
29+
30+
return ft.Column(
31+
[
32+
ft.Text(
33+
f"Winner: {winner}"
34+
if winner
35+
else f"Next player: {'X' if x_is_next else 'O'}"
36+
),
37+
ft.Row(
38+
[Square(squares[i], lambda e, i=i: handle_click(i)) for i in range(3)]
39+
),
40+
ft.Row(
41+
[
42+
Square(squares[i], lambda e, i=i: handle_click(i))
43+
for i in range(3, 6)
44+
]
45+
),
46+
ft.Row(
47+
[
48+
Square(squares[i], lambda e, i=i: handle_click(i))
49+
for i in range(6, 9)
50+
]
51+
),
52+
]
53+
)
54+
55+
56+
@ft.component
57+
def Game():
58+
history, set_history = ft.use_state([[""] * 9])
59+
current_move, set_current_move = ft.use_state(0)
60+
x_is_next = current_move % 2 == 0
61+
62+
def handle_play(next_squares: list[str]):
63+
next_history = history[: current_move + 1] + [next_squares]
64+
set_history(next_history)
65+
set_current_move(len(next_history) - 1)
66+
67+
def jump_to(move: int):
68+
set_current_move(move)
69+
70+
moves: list[ft.Control] = [
71+
ft.TextButton(
72+
ft.Text(f"Go to move #{move}" if move > 0 else "Go to game start"),
73+
on_click=lambda e, m=move: jump_to(m),
74+
)
75+
for move, _ in enumerate(history)
76+
]
77+
78+
return ft.Row(
79+
[
80+
Board(x_is_next, history[current_move], handle_play),
81+
ft.Column(moves),
82+
],
83+
vertical_alignment=ft.CrossAxisAlignment.START,
84+
)
85+
86+
87+
def calculate_winner(squares: list[str]):
88+
lines = [
89+
[0, 1, 2],
90+
[3, 4, 5],
91+
[6, 7, 8],
92+
[0, 3, 6],
93+
[1, 4, 7],
94+
[2, 5, 8],
95+
[0, 4, 8],
96+
[2, 4, 6],
97+
]
98+
for line in lines:
99+
a, b, c = line
100+
if squares[a] and squares[a] == squares[b] == squares[c]:
101+
return squares[a]
102+
return None
103+
104+
105+
ft.run(lambda page: page.render(Game))

sdk/python/packages/flet/src/flet/components/component.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import weakref
66
from collections import defaultdict
77
from dataclasses import dataclass, field
8-
from typing import Any, Callable, TypeVar
8+
from functools import wraps
9+
from typing import Any, Callable, ParamSpec, TypeVar
910

1011
from flet.components.hooks import EffectHook, Hook
1112
from flet.components.observable import Observable, ObservableSubscription
@@ -238,18 +239,23 @@ def __str__(self):
238239
#
239240

240241

241-
def component(fn: Callable[..., Any]) -> Callable[..., Any]:
242+
P = ParamSpec("P")
243+
R = TypeVar("R")
244+
245+
246+
def component(fn: Callable[P, R]) -> Callable[P, R]:
242247
"""
243248
Marks a function as a component. When called, it will render through
244249
the *current* Renderer.
245250
"""
246251
fn.__is_component__ = True
247252

248-
def component_wrapper(*args, key=None, **kwargs):
253+
@wraps(fn)
254+
def component_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
255+
key = kwargs.pop("key", None)
249256
r = _get_renderer()
250257
return r._render_component(fn, args, kwargs, key=key)
251258

252-
component_wrapper.__name__ = fn.__name__
253259
component_wrapper.__is_component__ = True
254260
component_wrapper.__component_impl__ = fn
255261
return component_wrapper

0 commit comments

Comments
 (0)