diff --git a/sdk/python/examples/controls/chip/__init__.py b/sdk/python/examples/controls/chip/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/python/examples/controls/chip/assist_chips.py b/sdk/python/examples/controls/chip/assist_chips.py index 7bcf78ca3f..188709c0cb 100644 --- a/sdk/python/examples/controls/chip/assist_chips.py +++ b/sdk/python/examples/controls/chip/assist_chips.py @@ -34,4 +34,5 @@ async def handle_chip2_click(e: ft.Event[ft.Chip]): ) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/chip/filter_chips.py b/sdk/python/examples/controls/chip/filter_chips.py index 16e84f61be..aaf1d71a3c 100644 --- a/sdk/python/examples/controls/chip/filter_chips.py +++ b/sdk/python/examples/controls/chip/filter_chips.py @@ -29,4 +29,5 @@ def handle_amenity_selection(e: ft.Event[ft.Chip]): ) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/tutorials/calculator/__init__.py b/sdk/python/examples/tutorials/calculator/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/python/examples/tutorials/calculator/calc.py b/sdk/python/examples/tutorials/calculator/calc.py index 9ebb3ec650..f699fead6a 100644 --- a/sdk/python/examples/tutorials/calculator/calc.py +++ b/sdk/python/examples/tutorials/calculator/calc.py @@ -1,100 +1,93 @@ +from dataclasses import field + import flet as ft +@ft.control class CalcButton(ft.Button): - def __init__(self, text, button_clicked, expand=1): - super().__init__() - self.text = text - self.expand = expand - self.on_click = button_clicked - self.data = text + expand: int = field(default_factory=lambda: 1) +@ft.control class DigitButton(CalcButton): - def __init__(self, text, button_clicked, expand=1): - CalcButton.__init__(self, text, button_clicked, expand) - self.bgcolor = ft.Colors.WHITE24 - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.WHITE_24 + color: ft.Colors = ft.Colors.WHITE +@ft.control class ActionButton(CalcButton): - def __init__(self, text, button_clicked): - CalcButton.__init__(self, text, button_clicked) - self.bgcolor = ft.Colors.ORANGE - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.ORANGE + color: ft.Colors = ft.Colors.WHITE +@ft.control class ExtraActionButton(CalcButton): - def __init__(self, text, button_clicked): - CalcButton.__init__(self, text, button_clicked) - self.bgcolor = ft.Colors.BLUE_GREY_100 - self.color = ft.Colors.BLACK + bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100 + color: ft.Colors = ft.Colors.BLACK +@ft.control class CalculatorApp(ft.Container): - # application's root control (i.e. "view") containing all other controls - def __init__(self): - super().__init__() + def init(self): self.reset() - - self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) self.width = 350 self.bgcolor = ft.Colors.BLACK - self.border_radius = ft.border_radius.all(20) + self.border_radius = ft.BorderRadius.all(20) self.padding = 20 + self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) + self.content = ft.Column( controls=[ - ft.Row(controls=[self.result], alignment="end"), + ft.Row( + controls=[self.result], + alignment=ft.MainAxisAlignment.END, + ), ft.Row( controls=[ - ExtraActionButton( - text="AC", button_clicked=self.button_clicked - ), - ExtraActionButton( - text="+/-", button_clicked=self.button_clicked - ), - ExtraActionButton(text="%", button_clicked=self.button_clicked), - ActionButton(text="/", button_clicked=self.button_clicked), + ExtraActionButton(content="AC", on_click=self.button_clicked), + ExtraActionButton(content="+/-", on_click=self.button_clicked), + ExtraActionButton(content="%", on_click=self.button_clicked), + ActionButton(content="/", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="7", button_clicked=self.button_clicked), - DigitButton(text="8", button_clicked=self.button_clicked), - DigitButton(text="9", button_clicked=self.button_clicked), - ActionButton(text="*", button_clicked=self.button_clicked), + DigitButton(content="7", on_click=self.button_clicked), + DigitButton(content="8", on_click=self.button_clicked), + DigitButton(content="9", on_click=self.button_clicked), + ActionButton(content="*", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="4", button_clicked=self.button_clicked), - DigitButton(text="5", button_clicked=self.button_clicked), - DigitButton(text="6", button_clicked=self.button_clicked), - ActionButton(text="-", button_clicked=self.button_clicked), + DigitButton(content="4", on_click=self.button_clicked), + DigitButton(content="5", on_click=self.button_clicked), + DigitButton(content="6", on_click=self.button_clicked), + ActionButton(content="-", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="1", button_clicked=self.button_clicked), - DigitButton(text="2", button_clicked=self.button_clicked), - DigitButton(text="3", button_clicked=self.button_clicked), - ActionButton(text="+", button_clicked=self.button_clicked), + DigitButton(content="1", on_click=self.button_clicked), + DigitButton(content="2", on_click=self.button_clicked), + DigitButton(content="3", on_click=self.button_clicked), + ActionButton(content="+", on_click=self.button_clicked), ] ), ft.Row( controls=[ DigitButton( - text="0", expand=2, button_clicked=self.button_clicked + content="0", expand=2, on_click=self.button_clicked ), - DigitButton(text=".", button_clicked=self.button_clicked), - ActionButton(text="=", button_clicked=self.button_clicked), + DigitButton(content=".", on_click=self.button_clicked), + ActionButton(content="=", on_click=self.button_clicked), ] ), ] ) def button_clicked(self, e): - data = e.control.data + data = e.control.content print(f"Button clicked with data = {data}") if self.result.value == "Error" or data == "AC": self.result.value = "0" diff --git a/sdk/python/examples/tutorials/calculator/calc1.py b/sdk/python/examples/tutorials/calculator/calc1.py index d0306ca28f..df8c33d47f 100644 --- a/sdk/python/examples/tutorials/calculator/calc1.py +++ b/sdk/python/examples/tutorials/calculator/calc1.py @@ -7,26 +7,27 @@ def main(page: ft.Page): page.add( result, - ft.Button(text="AC"), - ft.Button(text="+/-"), - ft.Button(text="%"), - ft.Button(text="/"), - ft.Button(text="7"), - ft.Button(text="8"), - ft.Button(text="9"), - ft.Button(text="*"), - ft.Button(text="4"), - ft.Button(text="5"), - ft.Button(text="6"), - ft.Button(text="-"), - ft.Button(text="1"), - ft.Button(text="2"), - ft.Button(text="3"), - ft.Button(text="+"), - ft.Button(text="0"), - ft.Button(text="."), - ft.Button(text="="), + ft.Button("AC"), + ft.Button("+/-"), + ft.Button("%"), + ft.Button("/"), + ft.Button("7"), + ft.Button("8"), + ft.Button("9"), + ft.Button("*"), + ft.Button("4"), + ft.Button("5"), + ft.Button("6"), + ft.Button("-"), + ft.Button("1"), + ft.Button("2"), + ft.Button("3"), + ft.Button("+"), + ft.Button("0"), + ft.Button("."), + ft.Button("="), ) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/tutorials/calculator/calc2.py b/sdk/python/examples/tutorials/calculator/calc2.py index 48be7dfa16..f32bf0113f 100644 --- a/sdk/python/examples/tutorials/calculator/calc2.py +++ b/sdk/python/examples/tutorials/calculator/calc2.py @@ -9,44 +9,45 @@ def main(page: ft.Page): ft.Row(controls=[result]), ft.Row( controls=[ - ft.Button(text="AC"), - ft.Button(text="+/-"), - ft.Button(text="%"), - ft.Button(text="/"), + ft.Button("AC"), + ft.Button("+/-"), + ft.Button("%"), + ft.Button("/"), ] ), ft.Row( controls=[ - ft.Button(text="7"), - ft.Button(text="8"), - ft.Button(text="9"), - ft.Button(text="*"), + ft.Button("7"), + ft.Button("8"), + ft.Button("9"), + ft.Button("*"), ] ), ft.Row( controls=[ - ft.Button(text="4"), - ft.Button(text="5"), - ft.Button(text="6"), - ft.Button(text="-"), + ft.Button("4"), + ft.Button("5"), + ft.Button("6"), + ft.Button("-"), ] ), ft.Row( controls=[ - ft.Button(text="1"), - ft.Button(text="2"), - ft.Button(text="3"), - ft.Button(text="+"), + ft.Button("1"), + ft.Button("2"), + ft.Button("3"), + ft.Button("+"), ] ), ft.Row( controls=[ - ft.Button(text="0"), - ft.Button(text="."), - ft.Button(text="="), + ft.Button("0"), + ft.Button("."), + ft.Button("="), ] ), ) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/tutorials/calculator/calc3.py b/sdk/python/examples/tutorials/calculator/calc3.py index c00d0ffe7d..90dd4ddf24 100644 --- a/sdk/python/examples/tutorials/calculator/calc3.py +++ b/sdk/python/examples/tutorials/calculator/calc3.py @@ -1,3 +1,5 @@ +from dataclasses import field + import flet as ft @@ -5,77 +7,72 @@ def main(page: ft.Page): page.title = "Calc App" result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) + @ft.control class CalcButton(ft.Button): - def __init__(self, text, expand=1): - super().__init__() - self.text = text - self.expand = expand + expand: int = field(default_factory=lambda: 1) + @ft.control class DigitButton(CalcButton): - def __init__(self, text, expand=1): - CalcButton.__init__(self, text, expand) - self.bgcolor = ft.Colors.WHITE24 - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.WHITE_24 + color: ft.Colors = ft.Colors.WHITE + @ft.control class ActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.ORANGE - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.ORANGE + color: ft.Colors = ft.Colors.WHITE + @ft.control class ExtraActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.BLUE_GREY_100 - self.color = ft.Colors.BLACK + bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100 + color: ft.Colors = ft.Colors.BLACK page.add( ft.Container( width=350, bgcolor=ft.Colors.BLACK, - border_radius=ft.border_radius.all(20), + border_radius=ft.BorderRadius.all(20), padding=20, content=ft.Column( controls=[ - ft.Row(controls=[result], alignment="end"), + ft.Row(controls=[result], alignment=ft.MainAxisAlignment.END), ft.Row( controls=[ - ExtraActionButton(text="AC"), - ExtraActionButton(text="+/-"), - ExtraActionButton(text="%"), - ActionButton(text="/"), + ExtraActionButton(content="AC"), + ExtraActionButton(content="+/-"), + ExtraActionButton(content="%"), + ActionButton(content="/"), ] ), ft.Row( controls=[ - DigitButton(text="7"), - DigitButton(text="8"), - DigitButton(text="9"), - ActionButton(text="*"), + DigitButton(content="7"), + DigitButton(content="8"), + DigitButton(content="9"), + ActionButton(content="*"), ] ), ft.Row( controls=[ - DigitButton(text="4"), - DigitButton(text="5"), - DigitButton(text="6"), - ActionButton(text="-"), + DigitButton(content="4"), + DigitButton(content="5"), + DigitButton(content="6"), + ActionButton(content="-"), ] ), ft.Row( controls=[ - DigitButton(text="1"), - DigitButton(text="2"), - DigitButton(text="3"), - ActionButton(text="+"), + DigitButton(content="1"), + DigitButton(content="2"), + DigitButton(content="3"), + ActionButton(content="+"), ] ), ft.Row( controls=[ - DigitButton(text="0", expand=2), - DigitButton(text="."), - ActionButton(text="="), - ] + DigitButton(content="0", expand=2), + DigitButton(content="."), + ActionButton(content="="), + ], ), ] ), @@ -83,4 +80,5 @@ def __init__(self, text): ) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/tutorials/calculator/calc4.py b/sdk/python/examples/tutorials/calculator/calc4.py index 80a6796f81..5808f29681 100644 --- a/sdk/python/examples/tutorials/calculator/calc4.py +++ b/sdk/python/examples/tutorials/calculator/calc4.py @@ -1,84 +1,82 @@ +from dataclasses import field + import flet as ft +@ft.control class CalcButton(ft.Button): - def __init__(self, text, expand=1): - super().__init__() - self.text = text - self.expand = expand + expand: int = field(default_factory=lambda: 1) +@ft.control class DigitButton(CalcButton): - def __init__(self, text, expand=1): - CalcButton.__init__(self, text, expand) - self.bgcolor = ft.Colors.WHITE24 - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.WHITE_24 + color: ft.Colors = ft.Colors.WHITE +@ft.control class ActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.ORANGE - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.ORANGE + color: ft.Colors = ft.Colors.WHITE +@ft.control class ExtraActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.BLUE_GREY_100 - self.color = ft.Colors.BLACK + bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100 + color: ft.Colors = ft.Colors.BLACK +@ft.control class CalculatorApp(ft.Container): - # application's root control (i.e. "view") containing all other controls - def __init__(self): - super().__init__() - - self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) + def init(self): self.width = 350 self.bgcolor = ft.Colors.BLACK - self.border_radius = ft.border_radius.all(20) + self.border_radius = ft.BorderRadius.all(20) self.padding = 20 + self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) self.content = ft.Column( controls=[ - ft.Row(controls=[self.result], alignment="end"), + ft.Row( + controls=[self.result], + alignment=ft.MainAxisAlignment.END, + ), ft.Row( controls=[ - ExtraActionButton(text="AC"), - ExtraActionButton(text="+/-"), - ExtraActionButton(text="%"), - ActionButton(text="/"), + ExtraActionButton(content="AC"), + ExtraActionButton(content="+/-"), + ExtraActionButton(content="%"), + ActionButton(content="/"), ] ), ft.Row( controls=[ - DigitButton(text="7"), - DigitButton(text="8"), - DigitButton(text="9"), - ActionButton(text="*"), + DigitButton(content="7"), + DigitButton(content="8"), + DigitButton(content="9"), + ActionButton(content="*"), ] ), ft.Row( controls=[ - DigitButton(text="4"), - DigitButton(text="5"), - DigitButton(text="6"), - ActionButton(text="-"), + DigitButton(content="4"), + DigitButton(content="5"), + DigitButton(content="6"), + ActionButton(content="-"), ] ), ft.Row( controls=[ - DigitButton(text="1"), - DigitButton(text="2"), - DigitButton(text="3"), - ActionButton(text="+"), + DigitButton(content="1"), + DigitButton(content="2"), + DigitButton(content="3"), + ActionButton(content="+"), ] ), ft.Row( controls=[ - DigitButton(text="0", expand=2), - DigitButton(text="."), - ActionButton(text="="), + DigitButton(content="0", expand=2), + DigitButton(content="."), + ActionButton(content="="), ] ), ] diff --git a/sdk/python/examples/tutorials/calculator/calc5.py b/sdk/python/examples/tutorials/calculator/calc5.py index 9ebb3ec650..f699fead6a 100644 --- a/sdk/python/examples/tutorials/calculator/calc5.py +++ b/sdk/python/examples/tutorials/calculator/calc5.py @@ -1,100 +1,93 @@ +from dataclasses import field + import flet as ft +@ft.control class CalcButton(ft.Button): - def __init__(self, text, button_clicked, expand=1): - super().__init__() - self.text = text - self.expand = expand - self.on_click = button_clicked - self.data = text + expand: int = field(default_factory=lambda: 1) +@ft.control class DigitButton(CalcButton): - def __init__(self, text, button_clicked, expand=1): - CalcButton.__init__(self, text, button_clicked, expand) - self.bgcolor = ft.Colors.WHITE24 - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.WHITE_24 + color: ft.Colors = ft.Colors.WHITE +@ft.control class ActionButton(CalcButton): - def __init__(self, text, button_clicked): - CalcButton.__init__(self, text, button_clicked) - self.bgcolor = ft.Colors.ORANGE - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.ORANGE + color: ft.Colors = ft.Colors.WHITE +@ft.control class ExtraActionButton(CalcButton): - def __init__(self, text, button_clicked): - CalcButton.__init__(self, text, button_clicked) - self.bgcolor = ft.Colors.BLUE_GREY_100 - self.color = ft.Colors.BLACK + bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100 + color: ft.Colors = ft.Colors.BLACK +@ft.control class CalculatorApp(ft.Container): - # application's root control (i.e. "view") containing all other controls - def __init__(self): - super().__init__() + def init(self): self.reset() - - self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) self.width = 350 self.bgcolor = ft.Colors.BLACK - self.border_radius = ft.border_radius.all(20) + self.border_radius = ft.BorderRadius.all(20) self.padding = 20 + self.result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) + self.content = ft.Column( controls=[ - ft.Row(controls=[self.result], alignment="end"), + ft.Row( + controls=[self.result], + alignment=ft.MainAxisAlignment.END, + ), ft.Row( controls=[ - ExtraActionButton( - text="AC", button_clicked=self.button_clicked - ), - ExtraActionButton( - text="+/-", button_clicked=self.button_clicked - ), - ExtraActionButton(text="%", button_clicked=self.button_clicked), - ActionButton(text="/", button_clicked=self.button_clicked), + ExtraActionButton(content="AC", on_click=self.button_clicked), + ExtraActionButton(content="+/-", on_click=self.button_clicked), + ExtraActionButton(content="%", on_click=self.button_clicked), + ActionButton(content="/", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="7", button_clicked=self.button_clicked), - DigitButton(text="8", button_clicked=self.button_clicked), - DigitButton(text="9", button_clicked=self.button_clicked), - ActionButton(text="*", button_clicked=self.button_clicked), + DigitButton(content="7", on_click=self.button_clicked), + DigitButton(content="8", on_click=self.button_clicked), + DigitButton(content="9", on_click=self.button_clicked), + ActionButton(content="*", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="4", button_clicked=self.button_clicked), - DigitButton(text="5", button_clicked=self.button_clicked), - DigitButton(text="6", button_clicked=self.button_clicked), - ActionButton(text="-", button_clicked=self.button_clicked), + DigitButton(content="4", on_click=self.button_clicked), + DigitButton(content="5", on_click=self.button_clicked), + DigitButton(content="6", on_click=self.button_clicked), + ActionButton(content="-", on_click=self.button_clicked), ] ), ft.Row( controls=[ - DigitButton(text="1", button_clicked=self.button_clicked), - DigitButton(text="2", button_clicked=self.button_clicked), - DigitButton(text="3", button_clicked=self.button_clicked), - ActionButton(text="+", button_clicked=self.button_clicked), + DigitButton(content="1", on_click=self.button_clicked), + DigitButton(content="2", on_click=self.button_clicked), + DigitButton(content="3", on_click=self.button_clicked), + ActionButton(content="+", on_click=self.button_clicked), ] ), ft.Row( controls=[ DigitButton( - text="0", expand=2, button_clicked=self.button_clicked + content="0", expand=2, on_click=self.button_clicked ), - DigitButton(text=".", button_clicked=self.button_clicked), - ActionButton(text="=", button_clicked=self.button_clicked), + DigitButton(content=".", on_click=self.button_clicked), + ActionButton(content="=", on_click=self.button_clicked), ] ), ] ) def button_clicked(self, e): - data = e.control.data + data = e.control.content print(f"Button clicked with data = {data}") if self.result.value == "Error" or data == "AC": self.result.value = "0" diff --git a/sdk/python/packages/flet/docs/tutorials/calculator.md b/sdk/python/packages/flet/docs/tutorials/calculator.md index 4cef4fb162..cea46b0309 100644 --- a/sdk/python/packages/flet/docs/tutorials/calculator.md +++ b/sdk/python/packages/flet/docs/tutorials/calculator.md @@ -1,10 +1,11 @@ --- title: Calculator Tutorial +examples: ../../examples/tutorials/calculator +example_images: ../test-images/tutorials/golden/macos/calculator --- In this tutorial you will learn, step-by-step, how to create a Calculator app in -Python using -Flet framework and publish it as a desktop, mobile or web app. +Python using Flet framework and publish it as a desktop, mobile or web app. The app is a simple console program, yet it is a multi-platform application with similar to iPhone calculator app UI: @@ -17,7 +18,6 @@ In this tutorial, we will cover all of the basic concepts for creating a Flet ap building a page layout, adding controls, making reusable UI components, handling events, and publishing options. -The tutorial consists of the following steps: ## Getting started with Flet @@ -56,42 +56,13 @@ and a few [`Button`][flet.Button]s with all the numbers and actions on them. Create `calc.py` with the following contents: -```python title="calc.py" -import flet as ft - -def main(page: ft.Page): - page.title = "Calc App" - result = ft.Text(value="0") - - page.add( - result, - ft.Button(text="AC"), - ft.Button(text="+/-"), - ft.Button(text="%"), - ft.Button(text="/"), - ft.Button(text="7"), - ft.Button(text="8"), - ft.Button(text="9"), - ft.Button(text="*"), - ft.Button(text="4"), - ft.Button(text="5"), - ft.Button(text="6"), - ft.Button(text="-"), - ft.Button(text="1"), - ft.Button(text="2"), - ft.Button(text="3"), - ft.Button(text="+"), - ft.Button(text="0"), - ft.Button(text="."), - ft.Button(text="="), - ) - -ft.run(main) +```python +--8<-- "{{ examples }}/calc1.py" ``` Run the app and you should see a page like this: -{{ image("../examples/tutorials/calculator/media/app-1.png", alt="app-1", width="80%") }} +{{ image(example_images + "/calc1.png", alt="calc1", width="50%") }} ## Building page layout @@ -100,63 +71,13 @@ Now let's arrange the text and buttons in 6 horizontal [`Row`][flet.Row]s. Replace `calc.py` contents with the following: -```python title="calc.py" -import flet as ft - - -def main(page: ft.Page): - page.title = "Calc App" - result = ft.Text(value="0") - - page.add( - ft.Row(controls=[result]), - ft.Row( - controls=[ - ft.Button(text="AC"), - ft.Button(text="+/-"), - ft.Button(text="%"), - ft.Button(text="/"), - ] - ), - ft.Row( - controls=[ - ft.Button(text="7"), - ft.Button(text="8"), - ft.Button(text="9"), - ft.Button(text="*"), - ] - ), - ft.Row( - controls=[ - ft.Button(text="4"), - ft.Button(text="5"), - ft.Button(text="6"), - ft.Button(text="-"), - ] - ), - ft.Row( - controls=[ - ft.Button(text="1"), - ft.Button(text="2"), - ft.Button(text="3"), - ft.Button(text="+"), - ] - ), - ft.Row( - controls=[ - ft.Button(text="0"), - ft.Button(text="."), - ft.Button(text="="), - ] - ), - ) - -ft.run(main) +```python +--8<-- "{{ examples }}/calc2.py" ``` Run the app and you should see a page like this: -{{ image("../examples/tutorials/calculator/media/app-2.png", alt="app-2", width="80%") }} +{{ image(example_images + "/calc2.png", alt="calc2", width="50%") }} ### Using Container for decoration @@ -175,7 +96,7 @@ Here is the code for adding the container to the page: ft.Container( width=350, bgcolor=ft.Colors.BLACK, - border_radius=ft.border_radius.all(20), + border_radius=ft.BorderRadius.all(20), padding=20, content=ft.Column( controls= [], # (1)! @@ -196,91 +117,95 @@ result = ft.Text(value="0", color=ft.Colors.WHITE, size=20) ``` For the buttons, if we look again at the UI we are aiming to achieve, there are 3 types of buttons: + 1. **Digit Buttons**. They have dark grey background color and white text, size is the same for all. + 2. **Action Buttons**. They have orange background color and white text, size is the same for all except `0` button which is twice as large. + 3. **Extra action buttons**. They have light grey background color and dark text, size is the same for all. The buttons will be used multiple time in the program, so we will be creating custom [Styled Controls](../cookbook/custom-controls.md#styled-controls) to reuse the code. Since all those types should inherit from `Button` class and have common `text` and `expand` properties, let's create a parent `CalcButton` class: + ```python +@ft.control class CalcButton(ft.Button): - def __init__(self, text, expand=1): - super().__init__() - self.text = text - self.expand = expand + expand: int = 1 ``` Now let's create child classes for all three types of buttons: ```python +@ft.control class DigitButton(CalcButton): - def __init__(self, text, expand=1): - CalcButton.__init__(self, text, expand) - self.bgcolor = ft.Colors.WHITE_24 - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.WHITE_24 + color: ft.Colors = ft.Colors.WHITE + +@ft.control class ActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.ORANGE - self.color = ft.Colors.WHITE + bgcolor: ft.Colors = ft.Colors.ORANGE + color: ft.Colors = ft.Colors.WHITE + +@ft.control class ExtraActionButton(CalcButton): - def __init__(self, text): - CalcButton.__init__(self, text) - self.bgcolor = ft.Colors.BLUE_GREY_100 - self.color = ft.Colors.BLACK + bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100 + color: ft.Colors = ft.Colors.BLACK ``` We will be using these new classes now to create rows of buttons in the Container: ```python -content=ft.Column( - controls=[ - ft.Row(controls=[result], alignment="end"), - ft.Row( - controls=[ - ExtraActionButton(text="AC"), - ExtraActionButton(text="+/-"), - ExtraActionButton(text="%"), - ActionButton(text="/"), - ] - ), - ft.Row( +content = ft.Column( controls=[ - DigitButton(text="7"), - DigitButton(text="8"), - DigitButton(text="9"), - ActionButton(text="*"), + ft.Row( + controls=[result], + alignment=ft.MainAxisAlignment.END, + ), + ft.Row( + controls=[ + ExtraActionButton(content="AC"), + ExtraActionButton(content="+/-"), + ExtraActionButton(content="%"), + ActionButton(content="/"), + ] + ), + ft.Row( + controls=[ + DigitButton(content="7"), + DigitButton(content="8"), + DigitButton(content="9"), + ActionButton(content="*"), + ] + ), + ft.Row( + controls=[ + DigitButton(content="4"), + DigitButton(content="5"), + DigitButton(content="6"), + ActionButton(content="-"), + ] + ), + ft.Row( + controls=[ + DigitButton(content="1"), + DigitButton(content="2"), + DigitButton(content="3"), + ActionButton(content="+"), + ] + ), + ft.Row( + controls=[ + DigitButton(content="0", expand=2), + DigitButton(content="."), + ActionButton(content="="), + ] + ), ] - ), - ft.Row( - controls=[ - DigitButton(text="4"), - DigitButton(text="5"), - DigitButton(text="6"), - ActionButton(text="-"), - ] - ), - ft.Row( - controls=[ - DigitButton(text="1"), - DigitButton(text="2"), - DigitButton(text="3"), - ActionButton(text="+"), - ] - ), - ft.Row( - controls=[ - DigitButton(text="0", expand=2), - DigitButton(text="."), - ActionButton(text="="), - ] - ), - ] -), + ) ``` /// details | Full code @@ -291,7 +216,7 @@ content=ft.Column( ``` /// -{{ image("../examples/tutorials/calculator/media/app.png", alt="calc-app", width="80%") }} +{{ image(example_images + "/calc3.png", alt="calc3", width="50%") }} Just what we wanted! @@ -310,7 +235,7 @@ Flet apps with composability and reusability in mind. To make a reusable Calc app component, we are going to encapsulate its state and presentation logic in a separate `CalculatorApp` class. -Copy the entire code for this step from [here](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/tutorials/calc/calc4.py). +Copy the entire code for this step from [here](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/tutorials/calculator/calc4.py). /// admonition | Try something type: example @@ -329,33 +254,24 @@ page.add(calc1, calc2) ## Handling events Now let's make the calculator do its job. We will be using the same event handler -for all the buttons and use `data` property to differentiate between the actions -depending on the button clicked. For `CalcButton` class, let's specify `on_click=button_clicked` -event and set `data` property equal to button's text: - -```python -class CalcButton(ft.Button): - def __init__(self, text, button_clicked, expand=1): - super().__init__() - self.text = text - self.expand = expand - self.on_click = button_clicked - self.data = text -``` +for all the buttons and use `content` property to differentiate between the actions +depending on the button clicked. We will define `button_click` method in `CalculatorClass` and pass it to each button. Below is `on_click` event handler that will reset the Text value when "AC" button is clicked: ```python def button_clicked(self, e): - if e.control.data == "AC": + data = e.control.content + print(f"Button clicked with data = {data}") + if data == "AC": self.result.value = "0" ``` With similar approach, `button_click` method will handle different calculator actions -depending on `data` property for each button. +depending on `content` property for each button. Copy the entire code for this step from -[here](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/tutorials/calc/calc.py). +[here](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/tutorials/calculator/calc.py). Run the app and see it in the action: {{ image("../examples/tutorials/calculator/media/app.gif", alt="calc-app2", width="80%") }} diff --git a/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc1.png b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc1.png new file mode 100644 index 0000000000..eda7ad147e Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc1.png differ diff --git a/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc2.png b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc2.png new file mode 100644 index 0000000000..e025d4a822 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc2.png differ diff --git a/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc3.png b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc3.png new file mode 100644 index 0000000000..0469aa44cf Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/tutorials/golden/macos/calculator/calc3.png differ diff --git a/sdk/python/packages/flet/integration_tests/tutorials/test_calculator.py b/sdk/python/packages/flet/integration_tests/tutorials/test_calculator.py new file mode 100644 index 0000000000..8929668ce6 --- /dev/null +++ b/sdk/python/packages/flet/integration_tests/tutorials/test_calculator.py @@ -0,0 +1,65 @@ +import pytest + +import flet as ft +import flet.testing as ftt +from examples.tutorials.calculator import calc1, calc2, calc3 + + +@pytest.mark.parametrize( + "flet_app_function", + [{"flet_app_main": calc1.main}], + indirect=True, +) +@pytest.mark.asyncio(loop_scope="function") +async def test_calc1(flet_app_function: ftt.FletTestApp): + flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.enable_screenshots = True + flet_app_function.resize_page(350, 850) + flet_app_function.page.update() + await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( + "calc1", + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) + + +@pytest.mark.parametrize( + "flet_app_function", + [{"flet_app_main": calc2.main}], + indirect=True, +) +@pytest.mark.asyncio(loop_scope="function") +async def test_calc2(flet_app_function: ftt.FletTestApp): + flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.enable_screenshots = True + flet_app_function.resize_page(350, 350) + flet_app_function.page.update() + await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( + "calc2", + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) + + +@pytest.mark.parametrize( + "flet_app_function", + [{"flet_app_main": calc3.main}], + indirect=True, +) +@pytest.mark.asyncio(loop_scope="function") +async def test_calc3(flet_app_function: ftt.FletTestApp): + flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.enable_screenshots = True + flet_app_function.resize_page(400, 350) + flet_app_function.page.update() + await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( + "calc3", + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) diff --git a/sdk/python/packages/flet/src/flet/controls/base_control.py b/sdk/python/packages/flet/src/flet/controls/base_control.py index c87fd3a620..1e20cf879f 100644 --- a/sdk/python/packages/flet/src/flet/controls/base_control.py +++ b/sdk/python/packages/flet/src/flet/controls/base_control.py @@ -3,7 +3,7 @@ import logging import sys from dataclasses import InitVar, dataclass, field -from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union, overload from flet.controls.context import _context_page, context from flet.controls.control_event import ControlEvent, get_event_field_type @@ -40,6 +40,20 @@ def skip_field(): T = TypeVar("T", bound="BaseControl") +@overload +def control(cls: type[T]) -> type[T]: ... + + +@overload +def control( + dart_widget_name: Optional[Union[type[T], str]] = None, + *, + isolated: Optional[bool] = None, + post_init_args: int = 1, + **dataclass_kwargs: Any, +) -> Callable[[type[T]], type[T]]: ... + + @dataclass_transform() def control( dart_widget_name: Optional[Union[type[T], str]] = None,