├── .DS_Store
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── day-001-band-name-generator
└── main.py
├── day-003-treasure-island
└── main.py
├── day-004-rock-paper-scissors
├── images
│ ├── paper_computer.png
│ ├── paper_user.png
│ ├── rock_computer.png
│ ├── rock_user.png
│ ├── scissors_computer.png
│ └── scissors_user.png
└── main.py
├── flet-solitaire
├── proof_of_concept1_images.py
├── proof_of_concept2.py
├── proof_of_concept3.py
├── proof_of_concept4.py
├── proof_of_concept5.py
├── proof_of_concept6.py
├── solitaire-classes
│ ├── card.py
│ ├── main.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-drag-and-drop
│ ├── step1.py
│ ├── step2.py
│ ├── step3.py
│ └── step4.py
├── solitaire-fanned-piles
│ ├── card.py
│ ├── main.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-final-part1
│ ├── card.py
│ ├── images
│ │ ├── 10_clubs.svg
│ │ ├── 10_diamonds.svg
│ │ ├── 10_hearts.svg
│ │ ├── 10_spades.svg
│ │ ├── 2_clubs.svg
│ │ ├── 2_diamonds.svg
│ │ ├── 2_hearts.svg
│ │ ├── 2_spades.svg
│ │ ├── 3_clubs.svg
│ │ ├── 3_diamonds.svg
│ │ ├── 3_hearts.svg
│ │ ├── 3_spades.svg
│ │ ├── 4_clubs.svg
│ │ ├── 4_diamonds.svg
│ │ ├── 4_hearts.svg
│ │ ├── 4_spades.svg
│ │ ├── 5_clubs.svg
│ │ ├── 5_diamonds.svg
│ │ ├── 5_hearts.svg
│ │ ├── 5_spades.svg
│ │ ├── 6_clubs.svg
│ │ ├── 6_diamonds.svg
│ │ ├── 6_hearts.svg
│ │ ├── 6_spades.svg
│ │ ├── 7_clubs.svg
│ │ ├── 7_diamonds.svg
│ │ ├── 7_hearts.svg
│ │ ├── 7_spades.svg
│ │ ├── 8_clubs.svg
│ │ ├── 8_diamonds.svg
│ │ ├── 8_hearts.svg
│ │ ├── 8_spades.svg
│ │ ├── 9_clubs.svg
│ │ ├── 9_diamonds.svg
│ │ ├── 9_hearts.svg
│ │ ├── 9_spades.svg
│ │ ├── Ace_clubs.svg
│ │ ├── Ace_diamonds.svg
│ │ ├── Ace_hearts.svg
│ │ ├── Ace_spades.svg
│ │ ├── Jack_clubs.svg
│ │ ├── Jack_diamonds.svg
│ │ ├── Jack_hearts.svg
│ │ ├── Jack_spades.svg
│ │ ├── King_clubs.svg
│ │ ├── King_diamonds.svg
│ │ ├── King_hearts.svg
│ │ ├── King_spades.svg
│ │ ├── Queen_clubs.svg
│ │ ├── Queen_diamonds.svg
│ │ ├── Queen_hearts.svg
│ │ ├── Queen_spades.svg
│ │ └── card_back.png
│ ├── main.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-final
│ ├── card.py
│ ├── images
│ │ ├── 10_clubs.svg
│ │ ├── 10_diamonds.svg
│ │ ├── 10_hearts.svg
│ │ ├── 10_spades.svg
│ │ ├── 2_clubs.svg
│ │ ├── 2_diamonds.svg
│ │ ├── 2_hearts.svg
│ │ ├── 2_spades.svg
│ │ ├── 3_clubs.svg
│ │ ├── 3_diamonds.svg
│ │ ├── 3_hearts.svg
│ │ ├── 3_spades.svg
│ │ ├── 4_clubs.svg
│ │ ├── 4_diamonds.svg
│ │ ├── 4_hearts.svg
│ │ ├── 4_spades.svg
│ │ ├── 5_clubs.svg
│ │ ├── 5_diamonds.svg
│ │ ├── 5_hearts.svg
│ │ ├── 5_spades.svg
│ │ ├── 6_clubs.svg
│ │ ├── 6_diamonds.svg
│ │ ├── 6_hearts.svg
│ │ ├── 6_spades.svg
│ │ ├── 7_clubs.svg
│ │ ├── 7_diamonds.svg
│ │ ├── 7_hearts.svg
│ │ ├── 7_spades.svg
│ │ ├── 8_clubs.svg
│ │ ├── 8_diamonds.svg
│ │ ├── 8_hearts.svg
│ │ ├── 8_spades.svg
│ │ ├── 9_clubs.svg
│ │ ├── 9_diamonds.svg
│ │ ├── 9_hearts.svg
│ │ ├── 9_spades.svg
│ │ ├── Ace_clubs.svg
│ │ ├── Ace_diamonds.svg
│ │ ├── Ace_hearts.svg
│ │ ├── Ace_spades.svg
│ │ ├── Jack_clubs.svg
│ │ ├── Jack_diamonds.svg
│ │ ├── Jack_hearts.svg
│ │ ├── Jack_spades.svg
│ │ ├── King_clubs.svg
│ │ ├── King_diamonds.svg
│ │ ├── King_hearts.svg
│ │ ├── King_spades.svg
│ │ ├── Queen_clubs.svg
│ │ ├── Queen_diamonds.svg
│ │ ├── Queen_hearts.svg
│ │ ├── Queen_spades.svg
│ │ ├── card.png
│ │ ├── card_back0.png
│ │ ├── card_back1.png
│ │ ├── card_back1.svg
│ │ ├── card_back2.png
│ │ ├── card_back2.svg
│ │ ├── card_back3.png
│ │ ├── card_back3.svg
│ │ └── card_back4.svg
│ ├── layout.py
│ ├── main.py
│ ├── settings.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-game-rules
│ ├── card.py
│ ├── images
│ │ ├── 10_clubs.svg
│ │ ├── 10_diamonds.svg
│ │ ├── 10_hearts.svg
│ │ ├── 10_spades.svg
│ │ ├── 2_clubs.svg
│ │ ├── 2_diamonds.svg
│ │ ├── 2_hearts.svg
│ │ ├── 2_spades.svg
│ │ ├── 3_clubs.svg
│ │ ├── 3_diamonds.svg
│ │ ├── 3_hearts.svg
│ │ ├── 3_spades.svg
│ │ ├── 4_clubs.svg
│ │ ├── 4_diamonds.svg
│ │ ├── 4_hearts.svg
│ │ ├── 4_spades.svg
│ │ ├── 5_clubs.svg
│ │ ├── 5_diamonds.svg
│ │ ├── 5_hearts.svg
│ │ ├── 5_spades.svg
│ │ ├── 6_clubs.svg
│ │ ├── 6_diamonds.svg
│ │ ├── 6_hearts.svg
│ │ ├── 6_spades.svg
│ │ ├── 7_clubs.svg
│ │ ├── 7_diamonds.svg
│ │ ├── 7_hearts.svg
│ │ ├── 7_spades.svg
│ │ ├── 8_clubs.svg
│ │ ├── 8_diamonds.svg
│ │ ├── 8_hearts.svg
│ │ ├── 8_spades.svg
│ │ ├── 9_clubs.svg
│ │ ├── 9_diamonds.svg
│ │ ├── 9_hearts.svg
│ │ ├── 9_spades.svg
│ │ ├── Ace_clubs.svg
│ │ ├── Ace_diamonds.svg
│ │ ├── Ace_hearts.svg
│ │ ├── Ace_spades.svg
│ │ ├── Jack_clubs.svg
│ │ ├── Jack_diamonds.svg
│ │ ├── Jack_hearts.svg
│ │ ├── Jack_spades.svg
│ │ ├── King_clubs.svg
│ │ ├── King_diamonds.svg
│ │ ├── King_hearts.svg
│ │ ├── King_spades.svg
│ │ ├── Queen_clubs.svg
│ │ ├── Queen_diamonds.svg
│ │ ├── Queen_hearts.svg
│ │ ├── Queen_spades.svg
│ │ └── card_back.png
│ ├── main.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-game-setup
│ ├── card.py
│ ├── images
│ │ ├── 10_clubs.svg
│ │ ├── 10_diamonds.svg
│ │ ├── 10_hearts.svg
│ │ ├── 10_spades.svg
│ │ ├── 2_clubs.svg
│ │ ├── 2_diamonds.svg
│ │ ├── 2_hearts.svg
│ │ ├── 2_spades.svg
│ │ ├── 3_clubs.svg
│ │ ├── 3_diamonds.svg
│ │ ├── 3_hearts.svg
│ │ ├── 3_spades.svg
│ │ ├── 4_clubs.svg
│ │ ├── 4_diamonds.svg
│ │ ├── 4_hearts.svg
│ │ ├── 4_spades.svg
│ │ ├── 5_clubs.svg
│ │ ├── 5_diamonds.svg
│ │ ├── 5_hearts.svg
│ │ ├── 5_spades.svg
│ │ ├── 6_clubs.svg
│ │ ├── 6_diamonds.svg
│ │ ├── 6_hearts.svg
│ │ ├── 6_spades.svg
│ │ ├── 7_clubs.svg
│ │ ├── 7_diamonds.svg
│ │ ├── 7_hearts.svg
│ │ ├── 7_spades.svg
│ │ ├── 8_clubs.svg
│ │ ├── 8_diamonds.svg
│ │ ├── 8_hearts.svg
│ │ ├── 8_spades.svg
│ │ ├── 9_clubs.svg
│ │ ├── 9_diamonds.svg
│ │ ├── 9_hearts.svg
│ │ ├── 9_spades.svg
│ │ ├── Ace_clubs.svg
│ │ ├── Ace_diamonds.svg
│ │ ├── Ace_hearts.svg
│ │ ├── Ace_spades.svg
│ │ ├── Jack_clubs.svg
│ │ ├── Jack_diamonds.svg
│ │ ├── Jack_hearts.svg
│ │ ├── Jack_spades.svg
│ │ ├── King_clubs.svg
│ │ ├── King_diamonds.svg
│ │ ├── King_hearts.svg
│ │ ├── King_spades.svg
│ │ ├── Queen_clubs.svg
│ │ ├── Queen_diamonds.svg
│ │ ├── Queen_hearts.svg
│ │ ├── Queen_spades.svg
│ │ └── card_back.png
│ ├── main.py
│ ├── slot.py
│ └── solitaire.py
├── solitaire-layout.drawio
├── solitaire-layout.png
├── solitaire-layout.svg
└── solitaire_animation_poc.py
├── hangman
└── hangman.py
├── test-animation-cards
├── images
│ ├── Jack_of_spades.svg
│ ├── ace-of-hearts.svg
│ ├── g29056.svg
│ ├── paper_computer.png
│ ├── paper_user.png
│ ├── path14770.svg
│ ├── rock_computer.png
│ ├── rock_user.png
│ ├── scissors_computer.png
│ └── scissors_user.png
└── test_controls.py
└── test_projects
├── .vscode
└── settings.json
├── colorpicker
├── customcolorpicker.py
├── main.py
└── palettecolorpicker.py
├── test.py
├── test_draggable_divider.py
├── test_draggable_vertical_divider.py
├── test_gradient.py
├── test_route2.py
└── test_routing.py
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/.DS_Store
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
105 | __pypackages__/
106 |
107 | # Celery stuff
108 | celerybeat-schedule
109 | celerybeat.pid
110 |
111 | # SageMath parsed files
112 | *.sage.py
113 |
114 | # Environments
115 | .env
116 | .venv
117 | env/
118 | venv/
119 | ENV/
120 | env.bak/
121 | venv.bak/
122 |
123 | # Spyder project settings
124 | .spyderproject
125 | .spyproject
126 |
127 | # Rope project settings
128 | .ropeproject
129 |
130 | # mkdocs documentation
131 | /site
132 |
133 | # mypy
134 | .mypy_cache/
135 | .dmypy.json
136 | dmypy.json
137 |
138 | # Pyre type checker
139 | .pyre/
140 |
141 | # pytype static type analyzer
142 | .pytype/
143 |
144 | # Cython debug symbols
145 | cython_debug/
146 |
147 | # PyCharm
148 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
149 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
150 | # and can be added to the global gitignore or merged into this file. For a more nuclear
151 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
152 | #.idea/
153 | .DS_Store
154 | .DS_Store
155 | .DS_Store
156 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Inesa Fitsner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flet-100-days-of-code
2 | 100 projects in Python with Flet
3 |
--------------------------------------------------------------------------------
/day-001-band-name-generator/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 |
4 | def main(page: ft.Page):
5 | page.title = "Flet Band Name Generator"
6 | # page.vertical_alignment = "center"
7 | def reset_textfields():
8 | city.error_text = ""
9 | pet.error_text = ""
10 |
11 | def button_clicked(e):
12 | reset_textfields()
13 | valid_form = True
14 |
15 | if city.value == "":
16 | city.error_text = "City cannot be blank."
17 | valid_form = False
18 | if pet.value == "":
19 | pet.error_text = "Pet name connot be blank."
20 | valid_form = False
21 |
22 | if valid_form:
23 | band_name.value = f"Your band name could be {city.value} {pet.value}."
24 | page.update()
25 |
26 | city = ft.TextField(label="Name of the city you grew up in")
27 | pet = ft.TextField(label="Name of your pet")
28 | band_name = ft.Text("Your band name could be [wait for it].")
29 |
30 | page.add(
31 | city,
32 | pet,
33 | ft.ElevatedButton(text="Generate your band name", on_click=button_clicked),
34 | band_name,
35 | )
36 |
37 |
38 | ft.app(target=main)
39 |
--------------------------------------------------------------------------------
/day-003-treasure-island/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 |
4 | def main(page: ft.Page):
5 | page.title = "Flet Treasure Island Story"
6 | # page.vertical_alignment = "center"
7 |
8 | def start_adventure(e):
9 | question1.visible = True
10 | left_button.visible = True
11 | right_button.visible = True
12 | welcome.visible = False
13 | directions.visible = False
14 | image.src = f"https://media.gettyimages.com/id/707544639/photo/t-intersection-sign-on-grassy-field-by-dirt-road-against-blue-sky.jpg?s=1024x1024&w=gi&k=20&c=uxrPO1TKBuOdCbbZ1q_eEXbym0LqBfHFhUiaMyw3eNA="
15 |
16 | start_adventure.visible = False
17 | page.update()
18 |
19 | def button_clicked(e):
20 | if e.control.data == "left":
21 | end_message.visible = True
22 |
23 | page.update()
24 |
25 | image = ft.Image(
26 | src=f"https://cdn.pixabay.com/photo/2015/02/01/17/06/treasure-chest-619868_1280.jpg",
27 | height=300,
28 | fit="fitWidth",
29 | )
30 |
31 | welcome = ft.Text(
32 | "Welcome to Treasure Island.",
33 | style="displayMedium",
34 | )
35 | directions = ft.Text("Your mission is to find the treasure.", style="titleMedium")
36 | start_adventure = ft.OutlinedButton(
37 | text="Start your adventure", on_click=start_adventure
38 | )
39 |
40 | left_button = ft.OutlinedButton(
41 | text="Left", on_click=button_clicked, data="left", visible=False
42 | )
43 | right_button = ft.OutlinedButton(
44 | text="Right", on_click=button_clicked, data="right", visible=False
45 | )
46 | question1 = ft.Text(
47 | "You came accross a T-intersection in a rural area. Where would you go, left or right?",
48 | style="titleMedium",
49 | visible=False,
50 | )
51 | end_message = ft.Text("You died.", visible=False)
52 | page.add(
53 | image,
54 | welcome,
55 | directions,
56 | start_adventure,
57 | question1,
58 | left_button,
59 | right_button,
60 | end_message,
61 | )
62 |
63 |
64 | ft.app(target=main)
65 |
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/paper_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/paper_computer.png
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/paper_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/paper_user.png
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/rock_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/rock_computer.png
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/rock_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/rock_user.png
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/scissors_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/scissors_computer.png
--------------------------------------------------------------------------------
/day-004-rock-paper-scissors/images/scissors_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/day-004-rock-paper-scissors/images/scissors_user.png
--------------------------------------------------------------------------------
/flet-solitaire/proof_of_concept1_images.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # This prototype is to move card to a space and if close enough drop it there;
4 | # if not return to original position
5 |
6 | class GameData:
7 | def __init__(self):
8 | self.start_top = 0
9 | self.start_left = 0
10 |
11 | def main(page: ft.Page):
12 | def move_on_top(item, list):
13 | list.remove(item)
14 | list.append(item)
15 |
16 | def start_drag(e: ft.DragStartEvent):
17 | move_on_top(e.control, controls)
18 | game_data.start_top = e.control.top
19 | game_data.start_left = e.control.left
20 | page.update()
21 |
22 | def check_proximity(e: ft.DragEndEvent):
23 | if (
24 | abs(e.control.top - space.top) < 20
25 | and abs(e.control.left - space.left) < 20
26 | ):
27 | e.control.top = space.top
28 | e.control.left = space.left
29 | else:
30 | e.control.top = game_data.start_top
31 | e.control.left = game_data.start_left
32 |
33 | page.update()
34 |
35 | def move(e: ft.DragUpdateEvent):
36 | e.control.top = max(0, e.control.top + e.delta_y)
37 | e.control.left = max(0, e.control.left + e.delta_x)
38 | e.control.update()
39 |
40 | space = ft.Container(
41 | width=50, height=50, left=200, top=200, border=ft.border.all(1)
42 | )
43 |
44 | card1 = ft.GestureDetector(
45 | mouse_cursor=ft.MouseCursor.MOVE,
46 | drag_interval=10,
47 | on_pan_update=move,
48 | on_pan_start=start_drag,
49 | on_pan_end=check_proximity,
50 | left=0,
51 | top=0,
52 | content=ft.Container(bgcolor=ft.colors.GREEN, width=50, height=50),
53 | )
54 |
55 | card2 = ft.GestureDetector(
56 | mouse_cursor=ft.MouseCursor.MOVE,
57 | drag_interval=10,
58 | on_pan_update=move,
59 | on_pan_start=start_drag,
60 | on_pan_end=check_proximity,
61 | left=100,
62 | top=100,
63 | content=ft.Container(
64 | content=ft.Image(src=f"/images/Ace_spades.svg"),
65 | width=70,
66 | height=100,
67 | border_radius=10,
68 | )
69 | )
70 |
71 | controls = [card1, card2, space]
72 | game_data = GameData()
73 |
74 | page.add(ft.Stack(controls, width=1000, height=500))
75 |
76 |
77 | ft.app(target=main, assets_dir="images")
78 |
--------------------------------------------------------------------------------
/flet-solitaire/proof_of_concept2.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # This prototype is to move card to any space and if close enough drop it there;
4 | # if not close to any space, return to original position
5 |
6 |
7 | class GameData:
8 | def __init__(self):
9 | self.start_top = 0
10 | self.start_left = 0
11 |
12 | def place_card(self, card, space):
13 | card.top = space.top
14 | card.left = space.left
15 |
16 | def bounce_back(self, card):
17 | card.top = self.start_top
18 | card.left = self.start_left
19 |
20 |
21 | def main(page: ft.Page):
22 | def move_on_top(item, list):
23 | """Brings draggable card to the top of the stack"""
24 | list.remove(item)
25 | list.append(item)
26 | page.update()
27 |
28 | def start_drag(e: ft.DragStartEvent):
29 | move_on_top(e.control, controls)
30 | # remember card original position to return it back if needed
31 | game_data.start_top = e.control.top
32 | game_data.start_left = e.control.left
33 | page.update()
34 |
35 | def drop(e: ft.DragEndEvent):
36 | # check if card is close to any of the spaces
37 | for space in spaces:
38 | if (
39 | abs(e.control.top - space.top) < 20
40 | and abs(e.control.left - space.left) < 20
41 | ):
42 | game_data.place_card(e.control, space)
43 | page.update()
44 | return
45 |
46 | # return card to original position
47 | game_data.bounce_back(e.control)
48 | page.update()
49 |
50 | def move(e: ft.DragUpdateEvent):
51 | e.control.top = max(0, e.control.top + e.delta_y)
52 | e.control.left = max(0, e.control.left + e.delta_x)
53 | e.control.update()
54 |
55 | spaces = []
56 |
57 | # top spaces (foundation piles)
58 | x = 0
59 | for i in range(3):
60 | spaces.append(
61 | ft.Container(width=65, height=100, left=x, top=0, border=ft.border.all(1))
62 | )
63 | x += 100
64 |
65 | # bottom spaces (plateau piles)
66 | y = 0
67 | for i in range(3):
68 | spaces.append(
69 | ft.Container(width=65, height=100, left=y, top=150, border=ft.border.all(1))
70 | )
71 | y += 100
72 |
73 | card1 = ft.GestureDetector(
74 | mouse_cursor=ft.MouseCursor.MOVE,
75 | drag_interval=10,
76 | on_pan_update=move,
77 | on_pan_start=start_drag,
78 | on_pan_end=drop,
79 | content=ft.Container(bgcolor=ft.colors.GREEN, width=65, height=100),
80 | )
81 |
82 | card2 = ft.GestureDetector(
83 | mouse_cursor=ft.MouseCursor.MOVE,
84 | drag_interval=10,
85 | on_pan_update=move,
86 | on_pan_start=start_drag,
87 | on_pan_end=drop,
88 | content=ft.Container(bgcolor=ft.colors.AMBER, width=65, height=100),
89 | )
90 |
91 | cards = [card1, card2]
92 | game_data = GameData()
93 |
94 | game_data.place_card(card1, spaces[4])
95 | game_data.place_card(card2, spaces[5])
96 |
97 | controls = spaces + cards
98 |
99 | page.add(ft.Stack(controls, width=1000, height=500))
100 |
101 |
102 | ft.app(target=main)
103 |
--------------------------------------------------------------------------------
/flet-solitaire/proof_of_concept3.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # This prototype is to move card to any space with or without card
4 | # If there is a card, drop it 20px lower than the top (upper) card
5 |
6 |
7 | class GameData:
8 | def __init__(self):
9 | self.start_top = 0
10 | self.start_left = 0
11 |
12 | def bounce_back(self, card):
13 | card.top = self.start_top
14 | card.left = self.start_left
15 |
16 |
17 | class Card:
18 | def __init__(self, control):
19 | self.control = control
20 | self.space = None
21 | self.set_control_data()
22 |
23 | def set_control_data(self):
24 | self.control.data = self
25 |
26 | def place(self, space):
27 |
28 | self.control.top = space.control.top + 20 * len(space.pile)
29 | self.control.left = space.control.left
30 |
31 | # remove the card form the old space's pile if exists
32 | if self.space is not None:
33 | self.space.pile.remove(self.control)
34 |
35 | # set card's space as new space
36 | self.space = space
37 |
38 | # add the card to the new space's pile
39 | space.pile.append(self.control)
40 |
41 |
42 | class Space:
43 | def __init__(self, space):
44 | self.control = space
45 | self.pile = []
46 | self.set_control_data()
47 |
48 | def set_control_data(self):
49 | self.control.data = self
50 |
51 | def upper_card_top(self):
52 | return self.control.top + 20 * len(self.pile)
53 |
54 |
55 | def main(page: ft.Page):
56 | def move_on_top(item, list):
57 | """Brings draggable card to the top of the stack"""
58 | list.remove(item)
59 | list.append(item)
60 | page.update()
61 |
62 | def start_drag(e: ft.DragStartEvent):
63 | move_on_top(e.control, controls)
64 | # remember card original position to return it back if needed
65 | game_data.start_top = e.control.top
66 | game_data.start_left = e.control.left
67 | page.update()
68 |
69 | def drop(e: ft.DragEndEvent):
70 | # check if card is close to any of the spaces
71 | for space in spaces:
72 | # top position of the upper card in the pile
73 | new_top = space.upper_card_top()
74 | if (
75 | abs(e.control.top - new_top) < 20
76 | and abs(e.control.left - space.control.left) < 20
77 | ):
78 | # place card to the space in proximity
79 | e.control.data.place(space)
80 | page.update()
81 | return
82 |
83 | # return card to original position
84 | game_data.bounce_back(e.control)
85 | page.update()
86 |
87 | def drag(e: ft.DragUpdateEvent):
88 | e.control.top = max(0, e.control.top + e.delta_y)
89 | e.control.left = max(0, e.control.left + e.delta_x)
90 | e.control.update()
91 |
92 | space_controls = []
93 | spaces = []
94 |
95 | # top spaces (foundation piles)
96 | x = 0
97 | for i in range(4):
98 | space_controls.append(
99 | ft.Container(width=65, height=100, left=x, top=0, border=ft.border.all(1))
100 | )
101 | spaces.append(Space(space_controls[-1]))
102 | x += 100
103 |
104 | # bottom spaces (plateau piles)
105 | y = 0
106 | for i in range(4):
107 | space_controls.append(
108 | ft.Container(width=65, height=100, left=y, top=150, border=ft.border.all(1))
109 | )
110 | spaces.append(Space(space_controls[-1]))
111 | y += 100
112 |
113 | colors = ["BLUE", "YELLOW", "GREEN", "RED"]
114 |
115 | card_controls = []
116 | cards = []
117 |
118 | for color in colors:
119 |
120 | card_controls.append(
121 | ft.GestureDetector(
122 | mouse_cursor=ft.MouseCursor.MOVE,
123 | drag_interval=10,
124 | on_pan_update=drag,
125 | on_pan_start=start_drag,
126 | on_pan_end=drop,
127 | content=ft.Container(width=65, height=100),
128 | )
129 | )
130 | card_controls[-1].content.bgcolor = color
131 | cards.append(Card(card_controls[-1]))
132 |
133 | game_data = GameData()
134 |
135 | for i in range(4):
136 | cards[i].place(spaces[4 + i])
137 |
138 | controls = space_controls + card_controls
139 |
140 | page.add(ft.Stack(controls, width=1000, height=500))
141 |
142 |
143 | ft.app(target=main)
144 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-classes/card.py:
--------------------------------------------------------------------------------
1 | CARD_WIDTH = 70
2 | CARD_HEIGTH = 100
3 | DROP_PROXIMITY = 20
4 |
5 | import flet as ft
6 |
7 | class Card(ft.GestureDetector):
8 | def __init__(self, solitaire, color):
9 | super().__init__()
10 | self.slot = None
11 | self.mouse_cursor=ft.MouseCursor.MOVE
12 | self.drag_interval=5
13 | self.on_pan_start=self.start_drag
14 | self.on_pan_update=self.drag
15 | self.on_pan_end=self.drop
16 | self.left=None
17 | self.top=None
18 | self.solitaire = solitaire
19 | self.color = color
20 | self.content=ft.Container(bgcolor=self.color, width=CARD_WIDTH, height=CARD_HEIGTH)
21 |
22 | def move_on_top(self):
23 | """Moves draggable card to the top of the stack"""
24 | self.solitaire.controls.remove(self)
25 | self.solitaire.controls.append(self)
26 | self.solitaire.update()
27 |
28 | def bounce_back(self):
29 | """Returns card to its original position"""
30 | self.top = self.slot.top
31 | self.left = self.slot.left
32 | self.update()
33 |
34 | def place(self, slot):
35 | """Place card to the slot"""
36 | self.top = slot.top
37 | self.left = slot.left
38 | self.slot = slot
39 |
40 | def start_drag(self, e: ft.DragStartEvent):
41 | self.move_on_top()
42 | self.update()
43 |
44 |
45 | def drag(self, e: ft.DragUpdateEvent):
46 | self.top = max(0, self.top + e.delta_y)
47 | self.left = max(0, self.left + e.delta_x)
48 | self.update()
49 |
50 | def drop(self, e: ft.DragEndEvent):
51 | for slot in self.solitaire.slots:
52 | if (
53 | abs(self.top - slot.top) < DROP_PROXIMITY
54 | and abs(self.left - slot.left) < DROP_PROXIMITY
55 | ):
56 | self.place(slot)
57 | self.update()
58 | return
59 |
60 | self.bounce_back()
61 | self.update()
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-classes/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from solitaire import Solitaire
3 |
4 |
5 | def main(page: ft.Page):
6 |
7 | solitaire = Solitaire()
8 |
9 | page.add(solitaire)
10 |
11 |
12 | ft.app(target=main)
13 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-classes/slot.py:
--------------------------------------------------------------------------------
1 | SLOT_WIDTH = 70
2 | SLOT_HEIGHT = 100
3 |
4 | import flet as ft
5 |
6 | class Slot(ft.Container):
7 | def __init__(self, top, left):
8 | super().__init__()
9 | self.pile=[]
10 | self.width=SLOT_WIDTH
11 | self.height=SLOT_HEIGHT
12 | self.left=left
13 | self.top=top
14 | self.border=ft.border.all(1)
15 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-classes/solitaire.py:
--------------------------------------------------------------------------------
1 | SOLITAIRE_WIDTH = 1000
2 | SOLITAIRE_HEIGHT = 500
3 |
4 | import flet as ft
5 | from slot import Slot
6 | from card import Card
7 |
8 | class Solitaire(ft.Stack):
9 | def __init__(self):
10 | super().__init__()
11 | self.controls = []
12 | self.slots = []
13 | self.cards = []
14 | self.width = SOLITAIRE_WIDTH
15 | self.height = SOLITAIRE_HEIGHT
16 |
17 | def did_mount(self):
18 | self.create_card_deck()
19 | self.create_slots()
20 | self.deal_cards()
21 |
22 | def create_card_deck(self):
23 | card1 = Card(self, color="GREEN")
24 | card2 = Card(self, color="YELLOW")
25 | self.cards = [card1, card2]
26 |
27 | def create_slots(self):
28 | self.slots.append(Slot(top=0, left=0))
29 | self.slots.append(Slot(top=0, left=200))
30 | self.slots.append(Slot(top=0, left=300))
31 | self.controls.extend(self.slots)
32 | self.update()
33 |
34 | def deal_cards(self):
35 | self.controls.extend(self.cards)
36 | for card in self.cards:
37 | card.place(self.slots[0])
38 | self.update()
39 |
40 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-drag-and-drop/step1.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # Use of GestureDetector for with on_pan_update event for dragging card
4 | # Absolute positioning of controls within stack
5 |
6 | def main(page: ft.Page):
7 |
8 | def drag(e: ft.DragUpdateEvent):
9 | e.control.top = max(0, e.control.top + e.delta_y)
10 | e.control.left = max(0, e.control.left + e.delta_x)
11 | e.control.update()
12 |
13 | card = ft.GestureDetector(
14 | mouse_cursor=ft.MouseCursor.MOVE,
15 | drag_interval=5,
16 | on_pan_update=drag,
17 | left=0,
18 | top=0,
19 | content=ft.Container(bgcolor=ft.colors.GREEN, width=70, height=100),
20 | )
21 |
22 | page.add(ft.Stack(controls=[card], width=1000, height=500))
23 |
24 | ft.app(target=main)
25 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-drag-and-drop/step2.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # Using Container for slot where the card should be dropped
4 | # on_pan_start event for the card: remember position of card to bounce it back on_pan_end of needed.
5 | # on_pan_end: check if card is in proximity of the slot and either place it to the slot or return to original position (bounce back).
6 | # Solitaire class created for holding original position coordinates
7 |
8 | class Solitaire:
9 | def __init__(self):
10 | self.start_top = 0
11 | self.start_left = 0
12 |
13 |
14 | def main(page: ft.Page):
15 | def place(card, slot):
16 | """place card to the slot"""
17 | card.top = slot.top
18 | card.left = slot.left
19 |
20 | def bounce_back(game, card):
21 | """return card to its original position"""
22 | card.top = game.start_top
23 | card.left = game.start_left
24 |
25 | def start_drag(e: ft.DragStartEvent):
26 | solitaire.start_top = e.control.top
27 | solitaire.start_left = e.control.left
28 |
29 | def drag(e: ft.DragUpdateEvent):
30 | e.control.top = max(0, e.control.top + e.delta_y)
31 | e.control.left = max(0, e.control.left + e.delta_x)
32 | e.control.update()
33 |
34 | def drop(e: ft.DragEndEvent):
35 | if (
36 | abs(e.control.top - slot.top) < 20
37 | and abs(e.control.left - slot.left) < 20
38 | ):
39 | place(e.control, slot)
40 |
41 | else:
42 | bounce_back(solitaire, e.control)
43 |
44 | e.control.update()
45 |
46 | slot = ft.Container(
47 | width=70, height=100, left=200, top=0, border=ft.border.all(1)
48 | )
49 |
50 | card = ft.GestureDetector(
51 | mouse_cursor=ft.MouseCursor.MOVE,
52 | drag_interval=5,
53 | on_pan_start=start_drag,
54 | on_pan_update=drag,
55 | on_pan_end=drop,
56 | left=0,
57 | top=0,
58 | content=ft.Container(bgcolor=ft.colors.GREEN, width=70, height=100),
59 | )
60 |
61 | solitaire = Solitaire()
62 |
63 | page.add(ft.Stack(controls = [slot, card], width=1000, height=500))
64 |
65 |
66 | ft.app(target=main)
67 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-drag-and-drop/step3.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # Adding secord card. Now the one of the cards will not be on top of stack when being dragged
4 | # move_on_top function to move the card on top on_pan_start event
5 |
6 | class Solitaire:
7 | def __init__(self):
8 | self.start_top = 0
9 | self.start_left = 0
10 |
11 |
12 | def main(page: ft.Page):
13 |
14 | def place(card, slot):
15 | """place card to the slot"""
16 | card.top = slot.top
17 | card.left = slot.left
18 |
19 | def bounce_back(game, card):
20 | """return card to its original position"""
21 | card.top = game.start_top
22 | card.left = game.start_left
23 |
24 | def move_on_top(card, controls):
25 | """Moves draggable card to the top of the stack"""
26 | controls.remove(card)
27 | controls.append(card)
28 | page.update()
29 |
30 | def start_drag(e: ft.DragStartEvent):
31 | move_on_top(e.control, controls)
32 | solitaire.start_top = e.control.top
33 | solitaire.start_left = e.control.left
34 |
35 |
36 | def drag(e: ft.DragUpdateEvent):
37 | e.control.top = max(0, e.control.top + e.delta_y)
38 | e.control.left = max(0, e.control.left + e.delta_x)
39 | e.control.update()
40 |
41 | def drop(e: ft.DragEndEvent):
42 | if (
43 | abs(e.control.top - slot.top) < 20
44 | and abs(e.control.left - slot.left) < 20
45 | ):
46 | place(e.control, slot)
47 | else:
48 | bounce_back(solitaire, e.control)
49 |
50 | e.control.update()
51 |
52 | slot = ft.Container(
53 | width=70, height=100, left=200, top=0, border=ft.border.all(1)
54 | )
55 |
56 | card1 = ft.GestureDetector(
57 | mouse_cursor=ft.MouseCursor.MOVE,
58 | drag_interval=5,
59 | on_pan_start=start_drag,
60 | on_pan_update=drag,
61 | on_pan_end=drop,
62 | left=0,
63 | top=0,
64 | content=ft.Container(bgcolor=ft.colors.GREEN, width=70, height=100),
65 | )
66 |
67 | card2 = ft.GestureDetector(
68 | mouse_cursor=ft.MouseCursor.MOVE,
69 | drag_interval=5,
70 | on_pan_start=start_drag,
71 | on_pan_update=drag,
72 | on_pan_end=drop,
73 | left=100,
74 | top=0,
75 | content=ft.Container(bgcolor=ft.colors.YELLOW, width=70, height=100),
76 | )
77 |
78 | solitaire = Solitaire()
79 |
80 | controls = [slot, card1, card2]
81 | page.add(ft.Stack(controls=controls, width=1000, height=500))
82 |
83 |
84 | ft.app(target=main)
85 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-drag-and-drop/step4.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # Adding 2 more slots
4 | # Deal cards before the beginning of the game
5 | # On_pan_end, go through slots list to find slot in proximity if possible
6 |
7 | class Solitaire:
8 | def __init__(self):
9 | self.start_top = 0
10 | self.start_left = 0
11 |
12 |
13 | def main(page: ft.Page):
14 |
15 | def place(card, slot):
16 | """place card to the slot"""
17 | card.top = slot.top
18 | card.left = slot.left
19 |
20 | def bounce_back(game, card):
21 | """return card to its original position"""
22 | card.top = game.start_top
23 | card.left = game.start_left
24 | page.update()
25 |
26 | def move_on_top(card, controls):
27 | """Moves draggable card to the top of the stack"""
28 | controls.remove(card)
29 | controls.append(card)
30 | page.update()
31 |
32 | def start_drag(e: ft.DragStartEvent):
33 | move_on_top(e.control, controls)
34 | solitaire.start_top = e.control.top
35 | solitaire.start_left = e.control.left
36 |
37 |
38 | def drag(e: ft.DragUpdateEvent):
39 | e.control.top = max(0, e.control.top + e.delta_y)
40 | e.control.left = max(0, e.control.left + e.delta_x)
41 | e.control.update()
42 |
43 | def drop(e: ft.DragEndEvent):
44 | for slot in slots:
45 | if (
46 | abs(e.control.top - slot.top) < 20
47 | and abs(e.control.left - slot.left) < 20
48 | ):
49 | place(e.control, slot)
50 | e.control.update()
51 | return
52 |
53 | bounce_back(solitaire, e.control)
54 | e.control.update()
55 |
56 |
57 | slot0 = ft.Container(
58 | width=70, height=100, left=0, top=0, border=ft.border.all(1)
59 | )
60 |
61 | slot1 = ft.Container(
62 | width=70, height=100, left=200, top=0, border=ft.border.all(1)
63 | )
64 |
65 | slot2 = ft.Container(
66 | width=70, height=100, left=300, top=0, border=ft.border.all(1)
67 | )
68 |
69 | slots = [slot0, slot1, slot2]
70 |
71 | card1 = ft.GestureDetector(
72 | mouse_cursor=ft.MouseCursor.MOVE,
73 | drag_interval=5,
74 | on_pan_start=start_drag,
75 | on_pan_update=drag,
76 | on_pan_end=drop,
77 | left=0,
78 | top=0,
79 | content=ft.Container(bgcolor=ft.colors.GREEN, width=70, height=100),
80 | )
81 |
82 | card2 = ft.GestureDetector(
83 | mouse_cursor=ft.MouseCursor.MOVE,
84 | drag_interval=5,
85 | on_pan_start=start_drag,
86 | on_pan_update=drag,
87 | on_pan_end=drop,
88 | left=100,
89 | top=0,
90 | content=ft.Container(bgcolor=ft.colors.YELLOW, width=70, height=100),
91 | )
92 |
93 | controls = [slot0, slot1, slot2, card1, card2]
94 |
95 | # deal cards
96 | place(card1, slot0)
97 | place(card2, slot0)
98 |
99 | solitaire = Solitaire()
100 |
101 | page.add(ft.Stack(controls=controls, width=1000, height=500))
102 |
103 |
104 | ft.app(target=main)
105 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-fanned-piles/card.py:
--------------------------------------------------------------------------------
1 | CARD_WIDTH = 70
2 | CARD_HEIGTH = 100
3 | DROP_PROXIMITY = 30
4 | CARD_OFFSET = 20
5 |
6 | import flet as ft
7 |
8 | class Card(ft.GestureDetector):
9 | def __init__(self, solitaire, color):
10 | super().__init__()
11 | self.mouse_cursor=ft.MouseCursor.MOVE
12 | self.drag_interval=5
13 | self.on_pan_start=self.start_drag
14 | self.on_pan_update=self.drag
15 | self.on_pan_end=self.drop
16 | self.left=None
17 | self.top=None
18 | self.solitaire = solitaire
19 | self.slot = None
20 | self.card_offset = CARD_OFFSET
21 | self.color = color
22 | self.content=ft.Container(bgcolor=self.color, width=CARD_WIDTH, height=CARD_HEIGTH)
23 |
24 | def move_on_top(self):
25 | """Brings draggable card pile to the top of the stack"""
26 |
27 | for card in self.get_draggable_pile():
28 | self.solitaire.controls.remove(card)
29 | self.solitaire.controls.append(card)
30 | self.solitaire.update()
31 |
32 | def bounce_back(self):
33 | """Returns draggable pile to its original position"""
34 | draggable_pile = self.get_draggable_pile()
35 | for card in draggable_pile:
36 | card.top = card.slot.top + card.slot.pile.index(card) * CARD_OFFSET
37 | card.left = card.slot.left
38 | self.solitaire.update()
39 |
40 | def place(self, slot):
41 | """Place draggable pile to the slot"""
42 |
43 | draggable_pile = self.get_draggable_pile()
44 |
45 | for card in draggable_pile:
46 | card.top = slot.top + len(slot.pile) * CARD_OFFSET
47 | card.left = slot.left
48 |
49 | # remove card from it's original slot, if exists
50 | if card.slot is not None:
51 | card.slot.pile.remove(card)
52 |
53 | # change card's slot to a new slot
54 | card.slot = slot
55 |
56 | # add card to the new slot's pile
57 | slot.pile.append(card)
58 |
59 | self.solitaire.update()
60 |
61 | def get_draggable_pile(self):
62 | """returns list of cards that will be dragged together, starting with the current card"""
63 | if self.slot is not None:
64 | return self.slot.pile[self.slot.pile.index(self):]
65 | return [self]
66 |
67 | def start_drag(self, e: ft.DragStartEvent):
68 | self.move_on_top()
69 | self.update()
70 |
71 |
72 | def drag(self, e: ft.DragUpdateEvent):
73 | draggable_pile = self.get_draggable_pile()
74 | for card in draggable_pile:
75 | card.top = max(0, self.top + e.delta_y) + draggable_pile.index(card) * CARD_OFFSET
76 | card.left = max(0, self.left + e.delta_x)
77 | card.update()
78 |
79 |
80 | def drop(self, e: ft.DragEndEvent):
81 | for slot in self.solitaire.slots:
82 | if (
83 | abs(self.top - (slot.top + len(slot.pile) * CARD_OFFSET))< DROP_PROXIMITY
84 | and abs(self.left - slot.left) < DROP_PROXIMITY
85 | ):
86 | self.place(slot)
87 | self.update()
88 | return
89 |
90 | self.bounce_back()
91 | self.update()
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-fanned-piles/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from solitaire import Solitaire
3 |
4 |
5 | def main(page: ft.Page):
6 |
7 | solitaire = Solitaire()
8 |
9 | page.add(solitaire)
10 |
11 | ft.app(target=main)
12 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-fanned-piles/slot.py:
--------------------------------------------------------------------------------
1 | SLOT_WIDTH = 70
2 | SLOT_HEIGHT = 100
3 |
4 | import flet as ft
5 |
6 | class Slot(ft.Container):
7 | def __init__(self, top, left):
8 | super().__init__()
9 | self.pile=[]
10 | self.width=SLOT_WIDTH
11 | self.height=SLOT_HEIGHT
12 | self.left=left
13 | self.top=top
14 | self.border=ft.border.all(1)
15 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-fanned-piles/solitaire.py:
--------------------------------------------------------------------------------
1 | #CARD_OFFSET = 20
2 | SOLITAIRE_WIDTH = 1000
3 | SOLITAIRE_HEIGHT = 500
4 |
5 | import flet as ft
6 | from slot import Slot
7 | from card import Card
8 |
9 | class Solitaire(ft.Stack):
10 | def __init__(self):
11 | super().__init__()
12 | #self.start_top = 0
13 | #self.start_left = 0
14 | self.controls = []
15 | self.slots = []
16 | #self.card_offset = CARD_OFFSET
17 | self.width = SOLITAIRE_WIDTH
18 | self.height = SOLITAIRE_HEIGHT
19 |
20 | def did_mount(self):
21 | self.create_card_deck()
22 | self.create_slots()
23 | self.deal_cards()
24 |
25 | def create_card_deck(self):
26 | card1 = Card(self, color="GREEN")
27 | card2 = Card(self, color="YELLOW")
28 | card3 = Card(self, color="RED")
29 | self.cards = [card1, card2, card3]
30 |
31 | def create_slots(self):
32 | self.slots.append(Slot(top=0, left=0))
33 | self.slots.append(Slot(top=0, left=200))
34 | self.slots.append(Slot(top=0, left=300))
35 | self.controls.extend(self.slots)
36 | self.update()
37 |
38 | def deal_cards(self):
39 | self.controls.extend(self.cards)
40 | for card in self.cards:
41 | card.place(self.slots[0])
42 | self.update()
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/2_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/Ace_clubs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/Ace_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/Ace_hearts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/Ace_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/images/card_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final-part1/images/card_back.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from solitaire import Solitaire
3 |
4 |
5 | def main(page: ft.Page):
6 |
7 | solitaire = Solitaire()
8 |
9 | page.add(solitaire)
10 |
11 | ft.app(target=main, assets_dir="images")
12 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/slot.py:
--------------------------------------------------------------------------------
1 | SLOT_WIDTH = 70
2 | SLOT_HEIGHT = 100
3 |
4 | import flet as ft
5 |
6 | class Slot(ft.Container):
7 | def __init__(self, solitaire, top, left, border):
8 | super().__init__()
9 | self.pile=[]
10 | self.width=SLOT_WIDTH
11 | self.height=SLOT_HEIGHT
12 | self.left=left
13 | self.top=top
14 | self.on_click=self.click
15 | self.solitaire=solitaire
16 | self.border=border
17 | self.border_radius = ft.border_radius.all(6)
18 |
19 | def get_top_card(self):
20 | if len(self.pile) > 0:
21 | return self.pile[-1]
22 |
23 | def click(self, e):
24 | if self == self.solitaire.stock:
25 | self.solitaire.restart_stock()
26 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final-part1/solitaire.py:
--------------------------------------------------------------------------------
1 | SOLITAIRE_WIDTH = 1000
2 | SOLITAIRE_HEIGHT = 500
3 |
4 | import flet as ft
5 | from slot import Slot
6 | from card import Card
7 | import random
8 |
9 | class Suite:
10 | def __init__(self, suite_name, suite_color):
11 | self.name = suite_name
12 | self.color = suite_color
13 |
14 | class Rank:
15 | def __init__(self, card_name, card_value):
16 | self.name = card_name
17 | self.value = card_value
18 |
19 | class Solitaire(ft.Stack):
20 | def __init__(self):
21 | super().__init__()
22 | self.controls = []
23 | self.width = SOLITAIRE_WIDTH
24 | self.height = SOLITAIRE_HEIGHT
25 |
26 | def did_mount(self):
27 | self.create_card_deck()
28 | self.create_slots()
29 | self.deal_cards()
30 |
31 | def create_card_deck(self):
32 | suites = [
33 | Suite("Hearts", "RED"),
34 | Suite("Diamonds", "RED"),
35 | Suite("Clubs", "BLACK"),
36 | Suite("Spades", "BLACK"),
37 | ]
38 | ranks = [
39 | Rank("Ace", 1),
40 | Rank("2", 2),
41 | Rank("3", 3),
42 | Rank("4", 4),
43 | Rank("5", 5),
44 | Rank("6", 6),
45 | Rank("7", 7),
46 | Rank("8", 8),
47 | Rank("9", 9),
48 | Rank("10", 10),
49 | Rank("Jack", 11),
50 | Rank("Queen", 12),
51 | Rank("King", 13),
52 | ]
53 |
54 | self.cards = []
55 |
56 | for suite in suites:
57 | for rank in ranks:
58 | self.cards.append(Card(solitaire=self, suite=suite, rank=rank))
59 |
60 | def create_slots(self):
61 | self.stock = Slot(solitaire=self, top=0, left=0, border=ft.border.all(1))
62 |
63 | self.waste = Slot(solitaire=self, top=0, left=100, border=None)
64 |
65 | self.foundations = []
66 | x = 300
67 | for i in range(4):
68 | self.foundations.append(Slot(solitaire=self, top=0, left=x, border=ft.border.all(1, "outline")))
69 | x += 100
70 |
71 | self.tableau = []
72 | x = 0
73 | for i in range(7):
74 | self.tableau.append(Slot(solitaire=self, top=150, left=x, border=None))
75 | x += 100
76 |
77 | self.controls.append(self.stock)
78 | self.controls.append(self.waste)
79 | self.controls.extend(self.foundations)
80 | self.controls.extend(self.tableau)
81 | self.update()
82 |
83 |
84 | def deal_cards(self):
85 | random.shuffle(self.cards)
86 | self.controls.extend(self.cards)
87 |
88 | # deal to tableau
89 | first_slot = 0
90 | remaining_cards = self.cards
91 |
92 | while first_slot < len(self.tableau):
93 | for slot in self.tableau[first_slot:]:
94 | top_card = remaining_cards[0]
95 | top_card.place(slot)
96 | remaining_cards.remove(top_card)
97 | first_slot +=1
98 |
99 | # place remaining cards to stock pile
100 | for card in remaining_cards:
101 | card.place(self.stock)
102 | print(f"Card in stock: {card.rank.name} {card.suite.name}")
103 |
104 | self.update()
105 |
106 | for slot in self.tableau:
107 | slot.get_top_card().turn_face_up()
108 |
109 | self.update()
110 |
111 | def check_foundations_rules(self, card, slot):
112 | top_card = slot.get_top_card()
113 | if top_card is not None:
114 | return (
115 | card.suite.name == top_card.suite.name
116 | and card.rank.value - top_card.rank.value == 1
117 | )
118 | else:
119 | return card.rank.name == "Ace"
120 |
121 | def check_tableau_rules(self, card, slot):
122 | top_card = slot.get_top_card()
123 | if top_card is not None:
124 | return (
125 | card.suite.color != top_card.suite.color
126 | and top_card.rank.value - card.rank.value == 1
127 | and top_card.face_up
128 | )
129 | else:
130 | return card.rank.name == "King"
131 |
132 | # def check_tableau_rules(self, card, slot):
133 | # return True
134 |
135 | def restart_stock(self):
136 | while len(self.waste.pile) > 0:
137 | card = self.waste.get_top_card()
138 | card.turn_face_down()
139 | card.move_on_top()
140 | card.place(self.stock)
141 | self.update
142 |
143 | def check_win(self):
144 | cards_num = 0
145 | for slot in self.foundations:
146 | cards_num += len(slot.pile)
147 | if cards_num == 52:
148 | return True
149 | return False
150 |
151 | def winning_sequence(self):
152 | for slot in self.foundations:
153 | for card in slot.pile:
154 | card.animate_position=2000
155 | card.move_on_top()
156 | card.top = random.randint(0, SOLITAIRE_HEIGHT)
157 | card.left = random.randint(0, SOLITAIRE_WIDTH)
158 | self.update()
159 | self.controls.append(ft.AlertDialog(title=ft.Text("Congratulations! You won!"), open=True))
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/2_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/4_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
130 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/Ace_clubs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/Ace_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/Ace_hearts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/Ace_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final/images/card.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final/images/card_back0.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final/images/card_back1.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final/images/card_back2.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back2.svg:
--------------------------------------------------------------------------------
1 |
2 |
86 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-final/images/card_back3.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/images/card_back3.svg:
--------------------------------------------------------------------------------
1 |
2 |
86 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/layout.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from settings import SettingsDialog
3 | def create_appbar(page, settings, on_new_game):
4 |
5 | def new_game_clicked(e):
6 | on_new_game(settings)
7 |
8 | def show_rules(e):
9 | page.dialog = rules_dialog
10 | rules_dialog.open = True
11 | page.update()
12 |
13 | def show_settings(e):
14 | page.dialog = SettingsDialog(settings, on_new_game)
15 | page.dialog.open = True
16 | page.update()
17 |
18 | page.appbar = ft.AppBar(
19 | leading=ft.Image(src=f"/images/card.png"),
20 | leading_width=30,
21 | title=ft.Text("Flet solitaire"),
22 | bgcolor=ft.colors.SURFACE_VARIANT,
23 | actions=[
24 | ft.TextButton(text="New game", on_click=new_game_clicked),
25 | ft.TextButton(text="Rules", on_click=show_rules),
26 | ft.IconButton(ft.icons.SETTINGS, on_click=show_settings),
27 |
28 | ],
29 | )
30 |
31 | rules_md = ft.Markdown(
32 | """
33 | Klondike is played with a standard 52-card deck, without Jokers.
34 |
35 | The four foundations (light rectangles in the upper right of the figure) are built up by suit from Ace (low in this game) to King, and the tableau piles can be built down by alternate colors. Every face-up card in a partial pile, or a complete pile, can be moved, as a unit, to another tableau pile on the basis of its highest card. Any empty piles can be filled with a King, or a pile of cards with a King. The aim of the game is to build up four stacks of cards starting with Ace and ending with King, all of the same suit, on one of the four foundations, at which time the player would have won. There are different ways of dealing the remainder of the deck from the stock to the waste, which can be selected in the Settings:
36 |
37 | - Turning three cards at once to the waste, with no limit on passes through the deck.
38 | - Turning three cards at once to the waste, with three passes through the deck.
39 | - Turning one card at a time to the waste, with three passes through the deck.
40 | - Turning one card at a time to the waste, with no limit on passes through the deck.
41 |
42 | If the player can no longer make any meaningful moves, the game is considered lost.
43 | """)
44 |
45 | rules_dialog = ft.AlertDialog(
46 | title=ft.Text("Solitaire rules"), content=rules_md, on_dismiss=lambda e: print("Dialog dismissed!")
47 | )
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | import flet as ft
5 | from solitaire import Solitaire
6 | from settings import Settings
7 | from layout import create_appbar
8 |
9 | # logging.basicConfig(level=logging.DEBUG)
10 |
11 |
12 | def main(page: ft.Page):
13 |
14 | def on_new_game(settings):
15 | page.controls.pop()
16 | new_solitaire = Solitaire(settings, on_win)
17 | page.add(new_solitaire)
18 | page.update()
19 |
20 | def on_win():
21 | page.add(ft.AlertDialog(title=ft.Text("YOU WIN!"), open=True))
22 | print("You win")
23 | page.update()
24 |
25 | settings = Settings()
26 | create_appbar(page, settings, on_new_game)
27 |
28 |
29 | solitaire = Solitaire(settings, on_win)
30 | page.add(solitaire)
31 |
32 |
33 | ft.app(target=main, assets_dir="images")
34 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/settings.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | class Settings:
4 | def __init__(self, waste_size=3, deck_passes_allowed=1000, card_back=f"/images/card_back0.png"):
5 | self.waste_size = waste_size
6 | self.deck_passes_allowed = deck_passes_allowed
7 | self.card_back = card_back
8 |
9 |
10 | class SettingsDialog(ft.AlertDialog):
11 | def __init__(self, settings, on_settings_applied):
12 | super().__init__()
13 | self.on_settings_applied = on_settings_applied
14 | self.settings = settings
15 | self.modal = True
16 | self.title = ft.Text("Solitare Settings")
17 | self.waste_size = ft.RadioGroup(value=self.settings.waste_size, content=ft.Row(controls=[
18 | ft.Radio(value=1, label="One card"),
19 | ft.Radio(value=3, label="Three cards")
20 | ]))
21 | self.deck_passes_allowed = ft.RadioGroup(value=self.settings.deck_passes_allowed, content=ft.Row(controls=[
22 | ft.Radio(value=3, label="Three"),
23 | ft.Radio(value=1000, label="Unlimited"),
24 | ]))
25 | self.generate_card_backs()
26 |
27 |
28 | self.content = ft.Column(controls=[
29 | ft.Text("Waste pile size:"),
30 | self.waste_size,
31 | ft.Text("Passes through the deck:"),
32 | self.deck_passes_allowed,
33 | ft.Row(controls=self.card_backs),
34 | ft.Checkbox(label="New game will be started when settings are updated.", value=True, disabled=True),
35 | ],
36 | tight=True
37 | )
38 | self.actions = [
39 | ft.TextButton("Cancel", on_click=self.cancel),
40 | ft.FilledButton("Apply settings", on_click=self.apply_settings),
41 | ]
42 |
43 | def generate_card_backs(self):
44 | self.card_backs = []
45 | for i in range(4):
46 | self.card_backs.append(ft.Container(width=70, height=100, content=ft.Image(src=f"/images/card_back{i}.png"), border_radius=ft.border_radius.all(6), on_click=self.choose_card_design, data=i))
47 | self.selected_card = self.card_backs[0]
48 |
49 |
50 | def choose_card_design(self, e):
51 | for card in self.card_backs:
52 | if card.data != e.control.data:
53 | card.border = None
54 | e.control.border = ft.border.all(3)
55 | self.selected_card = e.control
56 | self.update()
57 |
58 |
59 | def cancel(self, e):
60 | self.waste_size.value = self.settings.waste_size
61 | self.deck_passes_allowed.value = self.settings.deck_passes_allowed
62 | self.open = False
63 | self.update()
64 |
65 | def apply_settings(self, e):
66 | self.open = False
67 | self.settings.waste_size = int(self.waste_size.value)
68 | self.settings.deck_passes_allowed = int(self.deck_passes_allowed.value)
69 | self.settings.card_back = self.selected_card.content.src
70 | self.on_settings_applied(self.settings)
71 | self.update()
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-final/slot.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | class Slot(ft.Container):
4 | def __init__(self, solitaire, slot_type, top, left, border):
5 | super().__init__()
6 | self.solitaire = solitaire
7 | self.pile = []
8 | self.type = slot_type
9 | self.width = 70
10 | self.height = 100
11 | self.left = left
12 | self.top = top
13 | self.border_radius = ft.border_radius.all(6)
14 | self.border = border
15 | self.on_click = self.click
16 |
17 | def get_top_card(self):
18 | if len(self.pile) > 0:
19 | return self.pile[-1]
20 |
21 | def upper_card_top(self):
22 | if self.type == "tableau":
23 | if len(self.pile) > 1:
24 | return self.top + self.solitaire.card_offset * (len(self.pile) - 1)
25 | return self.top
26 |
27 | def click(self, e):
28 | if self.type == "stock" and self.solitaire.deck_passes_remaining > 1:
29 | self.solitaire.deck_passes_remaining -= 1
30 | self.solitaire.restart_stock()
31 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/2_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/4_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
130 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/Ace_clubs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/Ace_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/Ace_hearts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/Ace_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/images/card_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-game-rules/images/card_back.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from solitaire import Solitaire
3 |
4 |
5 | def main(page: ft.Page):
6 |
7 | solitaire = Solitaire()
8 |
9 | page.add(solitaire)
10 |
11 | ft.app(target=main, assets_dir="images")
12 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/slot.py:
--------------------------------------------------------------------------------
1 | SLOT_WIDTH = 70
2 | SLOT_HEIGHT = 100
3 |
4 | import flet as ft
5 |
6 | class Slot(ft.Container):
7 | def __init__(self, solitaire, top, left, border):
8 | super().__init__()
9 | self.pile=[]
10 | self.width=SLOT_WIDTH
11 | self.height=SLOT_HEIGHT
12 | self.left=left
13 | self.top=top
14 | self.on_click=self.click
15 | self.solitaire=solitaire
16 | self.border=border
17 | self.border_radius = ft.border_radius.all(6)
18 |
19 | def get_top_card(self):
20 | if len(self.pile) > 0:
21 | return self.pile[-1]
22 |
23 | def click(self, e):
24 | if self == self.solitaire.stock:
25 | self.solitaire.restart_stock()
26 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-rules/solitaire.py:
--------------------------------------------------------------------------------
1 | #CARD_OFFSET = 20
2 | SOLITAIRE_WIDTH = 1000
3 | SOLITAIRE_HEIGHT = 500
4 |
5 | import flet as ft
6 | from slot import Slot
7 | from card import Card
8 | import random
9 |
10 | class Suite:
11 | def __init__(self, suite_name, suite_color):
12 | self.name = suite_name
13 | self.color = suite_color
14 |
15 | class Rank:
16 | def __init__(self, card_name, card_value):
17 | self.name = card_name
18 | self.value = card_value
19 |
20 | class Solitaire(ft.Stack):
21 | def __init__(self):
22 | super().__init__()
23 | self.controls = []
24 | self.width = SOLITAIRE_WIDTH
25 | self.height = SOLITAIRE_HEIGHT
26 |
27 | def did_mount(self):
28 | self.create_card_deck()
29 | self.create_slots()
30 | self.deal_cards()
31 |
32 | def create_card_deck(self):
33 | suites = [
34 | Suite("Hearts", "RED"),
35 | Suite("Diamonds", "RED"),
36 | Suite("Clubs", "BLACK"),
37 | Suite("Spades", "BLACK"),
38 | ]
39 | ranks = [
40 | Rank("Ace", 1),
41 | Rank("2", 2),
42 | Rank("3", 3),
43 | Rank("4", 4),
44 | Rank("5", 5),
45 | Rank("6", 6),
46 | Rank("7", 7),
47 | Rank("8", 8),
48 | Rank("9", 9),
49 | Rank("10", 10),
50 | Rank("Jack", 11),
51 | Rank("Queen", 12),
52 | Rank("King", 13),
53 | ]
54 |
55 | self.cards = []
56 |
57 | for suite in suites:
58 | for rank in ranks:
59 | self.cards.append(Card(solitaire=self, suite=suite, rank=rank))
60 |
61 | def create_slots(self):
62 | self.stock = Slot(solitaire=self, top=0, left=0, border=ft.border.all(1))
63 |
64 | self.waste = Slot(solitaire=self, top=0, left=100, border=None)
65 |
66 | self.foundations = []
67 | x = 300
68 | for i in range(4):
69 | self.foundations.append(Slot(solitaire=self, top=0, left=x, border=ft.border.all(1, "outline")))
70 | x += 100
71 |
72 | self.tableau = []
73 | x = 0
74 | for i in range(7):
75 | self.tableau.append(Slot(solitaire=self, top=150, left=x, border=None))
76 | x += 100
77 |
78 | self.controls.append(self.stock)
79 | self.controls.append(self.waste)
80 | self.controls.extend(self.foundations)
81 | self.controls.extend(self.tableau)
82 | self.update()
83 |
84 |
85 | def deal_cards(self):
86 | random.shuffle(self.cards)
87 | self.controls.extend(self.cards)
88 |
89 | # deal to tableau
90 | first_slot = 0
91 | remaining_cards = self.cards
92 |
93 | while first_slot < len(self.tableau):
94 | for slot in self.tableau[first_slot:]:
95 | top_card = remaining_cards[0]
96 | top_card.place(slot)
97 | remaining_cards.remove(top_card)
98 | first_slot +=1
99 |
100 | # place remaining cards to stock pile
101 | for card in remaining_cards:
102 | card.place(self.stock)
103 | print(f"Card in stock: {card.rank.name} {card.suite.name}")
104 |
105 | self.update()
106 |
107 | for slot in self.tableau:
108 | slot.get_top_card().turn_face_up()
109 |
110 | self.update()
111 |
112 | def check_foundations_rules(self, card, slot):
113 | top_card = slot.get_top_card()
114 | if top_card is not None:
115 | return (
116 | card.suite.name == top_card.suite.name
117 | and card.rank.value - top_card.rank.value == 1
118 | )
119 | else:
120 | return card.rank.name == "Ace"
121 |
122 | def check_tableau_rules(self, card, slot):
123 | top_card = slot.get_top_card()
124 | if top_card is not None:
125 | return (
126 | card.suite.color != top_card.suite.color
127 | and top_card.rank.value - card.rank.value == 1
128 | and top_card.face_up
129 | )
130 | else:
131 | return card.rank.name == "King"
132 |
133 | def restart_stock(self):
134 | while len(self.waste.pile) > 0:
135 | card = self.waste.get_top_card()
136 | card.turn_face_down()
137 | card.move_on_top()
138 | card.place(self.stock)
139 | self.update
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/card.py:
--------------------------------------------------------------------------------
1 | CARD_WIDTH = 70
2 | CARD_HEIGTH = 100
3 | DROP_PROXIMITY = 30
4 | CARD_OFFSET = 20
5 |
6 | import flet as ft
7 |
8 | class Card(ft.GestureDetector):
9 | def __init__(self, solitaire, suite, rank):
10 | super().__init__()
11 | self.mouse_cursor=ft.MouseCursor.MOVE
12 | self.drag_interval=5
13 | self.on_pan_start=self.start_drag
14 | self.on_pan_update=self.drag
15 | self.on_pan_end=self.drop
16 | self.suite=suite
17 | self.rank=rank
18 | self.face_up=False
19 | self.top=None
20 | self.left=None
21 | self.solitaire = solitaire
22 | self.slot = None
23 | self.content=ft.Container(
24 | width=CARD_WIDTH,
25 | height=CARD_HEIGTH,
26 | border_radius = ft.border_radius.all(6),
27 | content=ft.Image(src="card_back.png"))
28 |
29 | def turn_face_up(self):
30 | """Reveals card"""
31 | self.face_up = True
32 | self.content.content.src=f"/images/{self.rank.name}_{self.suite.name}.svg"
33 | self.update()
34 |
35 | def move_on_top(self):
36 | """Brings draggable card pile to the top of the stack"""
37 |
38 | for card in self.get_draggable_pile():
39 | self.solitaire.controls.remove(card)
40 | self.solitaire.controls.append(card)
41 | self.solitaire.update()
42 |
43 | def bounce_back(self):
44 | """Returns draggable pile to its original position"""
45 | draggable_pile = self.get_draggable_pile()
46 | for card in draggable_pile:
47 | if card.slot in self.solitaire.tableau:
48 | card.top = card.slot.top + card.slot.pile.index(card) * CARD_OFFSET
49 | else:
50 | card.top = card.slot.top
51 | card.left = card.slot.left
52 | self.solitaire.update()
53 |
54 | def place(self, slot):
55 | """Place draggable pile to the slot"""
56 |
57 | draggable_pile = self.get_draggable_pile()
58 |
59 | for card in draggable_pile:
60 | if slot in self.solitaire.tableau:
61 | card.top = slot.top + len(slot.pile) * CARD_OFFSET
62 | else:
63 | card.top = slot.top
64 | card.left = slot.left
65 |
66 | # remove card from it's original slot, if exists
67 | if card.slot is not None:
68 | card.slot.pile.remove(card)
69 |
70 | # change card's slot to a new slot
71 | card.slot = slot
72 |
73 | # add card to the new slot's pile
74 | slot.pile.append(card)
75 |
76 | self.solitaire.update()
77 |
78 | def get_draggable_pile(self):
79 | """returns list of cards that will be dragged together, starting with the current card"""
80 | if self.slot is not None:
81 | return self.slot.pile[self.slot.pile.index(self):]
82 | return [self]
83 |
84 | def start_drag(self, e: ft.DragStartEvent):
85 | self.move_on_top()
86 | self.update()
87 |
88 |
89 | def drag(self, e: ft.DragUpdateEvent):
90 | draggable_pile = self.get_draggable_pile()
91 | for card in draggable_pile:
92 | card.top = max(0, self.top + e.delta_y) + draggable_pile.index(card) * CARD_OFFSET
93 | card.left = max(0, self.left + e.delta_x)
94 | card.update()
95 |
96 |
97 | def drop(self, e: ft.DragEndEvent):
98 | for slot in self.solitaire.tableau:
99 | if (
100 | abs(self.top - (slot.top + len(slot.pile) * CARD_OFFSET)) < DROP_PROXIMITY
101 | and abs(self.left - slot.left) < DROP_PROXIMITY
102 | ):
103 | self.place(slot)
104 | self.update()
105 | return
106 | for slot in self.solitaire.foundations:
107 | if (
108 | abs(self.top - slot.top) < DROP_PROXIMITY
109 | and abs(self.left - slot.left) < DROP_PROXIMITY
110 | ):
111 | self.place(slot)
112 | self.update()
113 | return
114 |
115 | self.bounce_back()
116 | self.update()
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/2_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/4_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
130 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/Ace_clubs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/Ace_diamonds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/Ace_hearts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/Ace_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/images/card_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-game-setup/images/card_back.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from solitaire import Solitaire
3 |
4 |
5 | def main(page: ft.Page):
6 |
7 | solitaire = Solitaire()
8 |
9 | page.add(solitaire)
10 |
11 | ft.app(target=main, assets_dir="images")
12 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/slot.py:
--------------------------------------------------------------------------------
1 | SLOT_WIDTH = 70
2 | SLOT_HEIGHT = 100
3 |
4 | import flet as ft
5 |
6 | class Slot(ft.Container):
7 | def __init__(self, top, left, border):
8 | super().__init__()
9 | self.pile=[]
10 | self.width=SLOT_WIDTH
11 | self.height=SLOT_HEIGHT
12 | self.left=left
13 | self.top=top
14 | self.border=border
15 | self.border_radius = ft.border_radius.all(6)
16 |
17 | def get_top_card(self):
18 | if len(self.pile) > 0:
19 | return self.pile[-1]
20 |
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-game-setup/solitaire.py:
--------------------------------------------------------------------------------
1 | #CARD_OFFSET = 20
2 | SOLITAIRE_WIDTH = 1000
3 | SOLITAIRE_HEIGHT = 500
4 |
5 | import flet as ft
6 | from slot import Slot
7 | from card import Card
8 | import random
9 |
10 | class Suite:
11 | def __init__(self, suite_name, suite_color):
12 | self.name = suite_name
13 | self.color = suite_color
14 |
15 | class Rank:
16 | def __init__(self, card_name, card_value):
17 | self.name = card_name
18 | self.value = card_value
19 |
20 | class Solitaire(ft.Stack):
21 | def __init__(self):
22 | super().__init__()
23 | self.controls = []
24 | self.width = SOLITAIRE_WIDTH
25 | self.height = SOLITAIRE_HEIGHT
26 |
27 | def did_mount(self):
28 | self.create_card_deck()
29 | self.create_slots()
30 | self.deal_cards()
31 |
32 | def create_card_deck(self):
33 | suites = [
34 | Suite("Hearts", "RED"),
35 | Suite("Diamonds", "RED"),
36 | Suite("Clubs", "BLACK"),
37 | Suite("Spades", "BLACK"),
38 | ]
39 | ranks = [
40 | Rank("Ace", 1),
41 | Rank("2", 2),
42 | Rank("3", 3),
43 | Rank("4", 4),
44 | Rank("5", 5),
45 | Rank("6", 6),
46 | Rank("7", 7),
47 | Rank("8", 8),
48 | Rank("9", 9),
49 | Rank("10", 10),
50 | Rank("Jack", 11),
51 | Rank("Queen", 12),
52 | Rank("King", 13),
53 | ]
54 |
55 | self.cards = []
56 |
57 | for suite in suites:
58 | for rank in ranks:
59 | self.cards.append(Card(solitaire=self, suite=suite, rank=rank))
60 |
61 | def create_slots(self):
62 | self.stock = Slot(top=0, left=0, border=ft.border.all(1))
63 |
64 | self.waste = Slot(top=0, left=100, border=None)
65 |
66 | self.foundations = []
67 | x = 300
68 | for i in range(4):
69 | self.foundations.append(Slot(top=0, left=x, border=ft.border.all(1, "outline")))
70 | x += 100
71 |
72 | self.tableau = []
73 | x = 0
74 | for i in range(7):
75 | self.tableau.append(Slot(top=150, left=x, border=None))
76 | x += 100
77 |
78 | self.controls.append(self.stock)
79 | self.controls.append(self.waste)
80 | self.controls.extend(self.foundations)
81 | self.controls.extend(self.tableau)
82 | self.update()
83 |
84 |
85 | def deal_cards(self):
86 | random.shuffle(self.cards)
87 | self.controls.extend(self.cards)
88 |
89 | # deal to tableau
90 | first_slot = 0
91 | remaining_cards = self.cards
92 |
93 | while first_slot < len(self.tableau):
94 | for slot in self.tableau[first_slot:]:
95 | top_card = remaining_cards[0]
96 | top_card.place(slot)
97 | remaining_cards.remove(top_card)
98 | first_slot +=1
99 |
100 | # place remaining cards to stock pile
101 | for card in remaining_cards:
102 | card.place(self.stock)
103 |
104 | self.update()
105 |
106 | for slot in self.tableau:
107 | slot.get_top_card().turn_face_up()
108 |
109 | self.update()
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-layout.drawio:
--------------------------------------------------------------------------------
1 | 7Vzfj+I2EP5rkK4PeyJxEuDxFm57qlR1JVa9u0eXmGARYpSYA/rX1yYOSezABtZgU3ginjg//M3M5/F4QgcMF5vfU7ic/UlCFHfcbrjpgFHHdZ0+8NgPl2yFJHAHuSRKcShkpWCM/0VC2BXSFQ5RVutICYkpXtaFE5IkaEJrMpimZF3vNiVx/alLGCFFMJ7AWJV+xyGd5dK+3y3l3xCOZsWTna44s4BFZyHIZjAk64oIfO2AYUoIzY8WmyGKOXoFLvl1LwfO7l8sRQltc0E3ipIFHkTBC/07+PZH/+VtPn8Sd/kF45UYsHhZui0QSMkqCRG/SbcDntczTNF4CSf87JopnclmdBGzlsMOM5qSOfqLnceUq5zDAZ7xogqT6xWiAtGdRLwJSinaHByisweOmRwiC0TTLesiLvAE1MLY+qK5LhXXGwjZrKI0LxBCKIwl2t+5xJMdCEhPgdfXjG8Is9mur6MHMb+OmBuokAVNkDngYpAFlkMGJMwGKmTgupC5mhHTAFJQx2jPilVXbMKoeymMgH0YOdaB5NkHErAOJN2Urt/b3J5nGCPdHH4BbzMPUs8+kOyzpL59IMmUZB6kgX0gedaBVKwobULJtw8l3StCHdObfShZGHLLHmc+UnIsDLplj7MAJQujbtnjLECpKVoKYsqHzsYSRPxoTGJMIU4R6xjDLVnRog97ZtlNgZcBRZvyWEMSk5RJEpKwns9THMeSKOOZriRiAr9svRGmkdGTe0hXhOlmGu9ygTMchihhsrqGdecsmpIUTlOSwr+Y/tRAbkzJZM6HgZke9KkExjhiiI4mDBvE5M8cOTyB8RdxYsEw549p1M0RPUxJQkWC2gk0hZKymylaChqUdLncmxpIvnA8IMUkEZrK7lRVfl8KQgzrylUDte8wo+hGHcq/VE7NsJZaBIooCb/wnSoOcQyzDE/qeqojiDaY/uDHn33R+lk5M9pUG1vRyB+JQmWrS4KVvRZZpRN0jCJEaofCNEL0WMdBs6IqmvCPTEApihnr/Kq/cJN6xBNeCWZDqewESd7alzScD1RcVSpZvZG8QyKbSg6EcqOdteyH/QEDahFDn2ZAsteVBgX6QdWkPnedo1bFG68oxWyInBZ0W5rb1tD6VhlaT5ehuVc2tBbLEF2GZoS5QEt7OjTDPOzpNHtS90ne4D8xgism/DSFScIMRYSUv91ptAJ8v6YjD6jhiuMdMTb98UqLnRttLODVp5vBIDA53xS1Gu8ThG8VQQx0EYR3ZYJQMzqfKFl2eNqENfgwhzyPg6a0lN0cU7gfZIqCGQyvY8Bp6xiB8EFS2NfCKdjxxiukTCHJTuLugtBsjuhkVly8029RO9YEOAM13f4oaIM3flYbJb3sWheINQrbfp9LTFKJUycApUCpLZNIa255qXVhHgHqCmnHI1ypJYPsShHvjj32iq5ryDibnLbWuHc2yS3cGEvooomD9W7XIgp1RdJIFOB+iQLYRhSnLUfuniiMrkyALqI4WPN5LaJQt8EaiSK4X6KQCwRME0Xx/AdRtCMKo5szgbaFh2Gi8NQ92Eai8O6XKDzHMqJoUb5/60TRvNFyfDNPI7kUXvH+zp3R7Mf/JpHqqUFyLZHq+I9UaoWRnlxJXaZrRLwWH4LcOiXppJfAJGtI0xnoemeGLgdK2q/FGWo5ZgvOuOMUqlz9Z5w0Wnzz8yANyd5tSaGezRoHP2K8Em0UeDxoo+3Oi2284bf4vuvBG5LBm+INbdGG6XDDd8/hjUd+Va3eM8Ybp9Uh3z1vGE1tyAnW81cppnnj3KLkjL0YPaFK8cL22NayTBlMTwpTfDmF1dZg5BtduRbIV7O71W9CBXLF/OIXGcDbml5SQvMP8sBoUNLWh6abnpwTa/gfMKfJ/uSicn3zzbmFyA/PP8nzpb/M238ef6rj98E7N7q056tJ1Krn52BWFqQ36fkaPF2urfCbPB3oiSxZs/x7y1zR5b+Egq//AQ==
--------------------------------------------------------------------------------
/flet-solitaire/solitaire-layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/flet-solitaire/solitaire-layout.png
--------------------------------------------------------------------------------
/flet-solitaire/solitaire_animation_poc.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | # Use of GestureDetector for with on_pan_update event for dragging card
4 | # Absolute positioning of controls within stack
5 |
6 | def main(page: ft.Page):
7 |
8 | def drag(e: ft.DragUpdateEvent):
9 | e.control.top = max(0, e.control.top + e.delta_y)
10 | e.control.left = max(0, e.control.left + e.delta_x)
11 | e.control.update()
12 |
13 | card = ft.GestureDetector(
14 | mouse_cursor=ft.MouseCursor.MOVE,
15 | drag_interval=5,
16 | on_pan_update=drag,
17 | left=0,
18 | top=0,
19 | animate_position=1000,
20 | content=ft.Container(bgcolor=ft.colors.GREEN, width=70, height=100),
21 | )
22 |
23 | page.add(ft.Stack(controls=[card], width=1000, height=500))
24 |
25 | ft.app(target=main)
26 |
--------------------------------------------------------------------------------
/test-animation-cards/images/ace-of-hearts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/test-animation-cards/images/paper_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/paper_computer.png
--------------------------------------------------------------------------------
/test-animation-cards/images/paper_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/paper_user.png
--------------------------------------------------------------------------------
/test-animation-cards/images/path14770.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
38 |
--------------------------------------------------------------------------------
/test-animation-cards/images/rock_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/rock_computer.png
--------------------------------------------------------------------------------
/test-animation-cards/images/rock_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/rock_user.png
--------------------------------------------------------------------------------
/test-animation-cards/images/scissors_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/scissors_computer.png
--------------------------------------------------------------------------------
/test-animation-cards/images/scissors_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InesaFitsner/flet-100-days-of-code/ad3eeafb075b5118f03090211f5665975fdb7092/test-animation-cards/images/scissors_user.png
--------------------------------------------------------------------------------
/test-animation-cards/test_controls.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 |
4 | class GameData:
5 | """
6 | To store user_choice and computer_choice
7 | """
8 |
9 | pass
10 |
11 |
12 | def main(page: ft.Page):
13 |
14 | page.horizontal_alignment = "start"
15 | page.vertical_alignment = "start"
16 |
17 | game_data = GameData()
18 |
19 | user_choice_text = ft.Text("Choose your weapon:", style="titleLarge")
20 | computer_choice_text = ft.Text("Computer choice:", style="titleLarge")
21 |
22 | def user_chose(e):
23 | """
24 | Will save control data as game_data.user_choice (0 for rock, 1 for paper, 2 for scissors) and show animation
25 | """
26 | print(f"user chose {e.control.data}")
27 | game_data.user_choice = int(e.control.data)
28 |
29 | c2.top = 50
30 | c2.left = 50
31 | c3.top = 50
32 | c3.left = 50
33 | page.update()
34 |
35 | c1 = ft.Container(
36 | content=ft.Image(src=f"/images/rock_user.png"),
37 | border_radius=10,
38 | border=ft.border.all(3, ft.colors.BLACK),
39 | # ink=True,
40 | data="0",
41 | width=150,
42 | height=150,
43 | bgcolor=ft.colors.WHITE,
44 | left=50,
45 | top=50,
46 | on_click=user_chose,
47 | animate_position=1000,
48 | )
49 |
50 | c2 = ft.Container(
51 | width=150,
52 | height=150,
53 | data="1",
54 | bgcolor=ft.colors.WHITE,
55 | border_radius=10,
56 | border=ft.border.all(3, ft.colors.BLACK),
57 | left=300,
58 | top=50,
59 | on_click=user_chose,
60 | animate_position=1000,
61 | content=ft.Image(src=f"/images/paper_user.png"),
62 | )
63 |
64 | c3 = ft.Container(
65 | width=150,
66 | height=150,
67 | bgcolor=ft.colors.WHITE,
68 | border_radius=10,
69 | border=ft.border.all(3, ft.colors.BLACK),
70 | left=550,
71 | top=50,
72 | animate_position=1000,
73 | data="2",
74 | on_click=user_chose,
75 | content=ft.Image(src=f"/images/scissors_user.png"),
76 | )
77 |
78 | page.add(
79 | ft.Row(
80 | width=800,
81 | alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
82 | controls=[user_choice_text, computer_choice_text],
83 | ),
84 | ft.Container(
85 | bgcolor=ft.colors.BLUE_GREY_100,
86 | width=800,
87 | height=250,
88 | content=ft.Stack(
89 | controls=[
90 | c1,
91 | c2,
92 | c3,
93 | ],
94 | ),
95 | ),
96 | )
97 |
98 |
99 | ft.app(target=main, assets_dir="images")
100 |
--------------------------------------------------------------------------------
/test_projects/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[python]": {
3 | "editor.defaultFormatter": "ms-python.black-formatter"
4 | },
5 | "python.formatting.provider": "none"
6 | }
--------------------------------------------------------------------------------
/test_projects/colorpicker/main.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from customcolorpicker import CustomColorPicker
3 | from palettecolorpicker import PaletteColorPicker
4 |
5 |
6 | def main(page: ft.Page):
7 | color_picker = CustomColorPicker(color="#c8df6f")
8 |
9 | d = ft.AlertDialog(content=color_picker)
10 | page.dialog = d
11 |
12 | def open_color_picker(e):
13 | d.open = True
14 | page.update()
15 |
16 | page.add(ft.IconButton(icon=ft.icons.BRUSH, on_click=open_color_picker))
17 |
18 |
19 | ft.app(target=main)
20 |
--------------------------------------------------------------------------------
/test_projects/colorpicker/palettecolorpicker.py:
--------------------------------------------------------------------------------
1 | from customcolorpicker import CustomColorPicker
2 | import flet as ft
3 |
4 |
5 | class ColorSwatch:
6 | def __init__(self, name, display_name, accent=True):
7 | self.name = name
8 | self.display_name = display_name
9 | self.accent = accent
10 |
11 |
12 | class Color:
13 | def __init__(self, swatch, shade="", accent=False):
14 | if shade == "":
15 | self.name = swatch.name
16 | self.display_name = swatch.display_name
17 | else:
18 | if not accent:
19 | self.name = f"{swatch.name}{shade}"
20 | self.display_name = f"{swatch.display_name}_{shade}"
21 | else:
22 | self.name = f"{swatch.name}accent{shade}"
23 | self.display_name = f"{swatch.display_name}_ACCENT_{shade}"
24 |
25 |
26 | SHADES = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900"]
27 | ACCENT_SHADES = ["100", "200", "400", "700"]
28 | WHITE_SHADES = ["10", "12", "24", "30", "38", "54", "70"]
29 | BLACK_SHADES = ["12", "26", "38", "45", "54", "87"]
30 |
31 |
32 | class PaletteColorPicker(ft.Row):
33 | def __init__(self, color="black"):
34 | super().__init__()
35 | self.tight = True
36 | self.color = color
37 | self.spacing = 1
38 | self.generate_color_matrix()
39 |
40 | def generate_color_matrix(self):
41 | swatches = [
42 | ColorSwatch(name="red", display_name="RED"),
43 | ColorSwatch(name="pink", display_name="PINK"),
44 | ColorSwatch(name="purple", display_name="PURPLE"),
45 | ColorSwatch(name="deeppurple", display_name="DEEP_PURPLE"),
46 | ColorSwatch(name="indigo", display_name="INDIGO"),
47 | ColorSwatch(name="blue", display_name="BLUE"),
48 | ColorSwatch(name="lightblue", display_name="LIGHT_BLUE"),
49 | ColorSwatch(name="cyan", display_name="CYAN"),
50 | ColorSwatch(name="teal", display_name="TEAL"),
51 | ColorSwatch(name="green", display_name="GREEN"),
52 | ColorSwatch(name="lightgreen", display_name="LIGHT_GREEN"),
53 | ColorSwatch(name="lime", display_name="LIME"),
54 | ColorSwatch(name="yellow", display_name="YELLOW"),
55 | ColorSwatch(name="amber", display_name="AMBER"),
56 | ColorSwatch(name="orange", display_name="ORANGE"),
57 | ColorSwatch(name="deeporange", display_name="DEEP_ORANGE"),
58 | ColorSwatch(name="brown", display_name="BROWN", accent=False),
59 | ColorSwatch(name="grey", display_name="GREY", accent=False),
60 | ColorSwatch(name="bluegrey", display_name="BLUE_GREY", accent=False),
61 | ColorSwatch(name="white", display_name="WHITE"),
62 | ColorSwatch(name="black", display_name="BLACK"),
63 | ]
64 |
65 | def generate_color_names(swatch):
66 | colors = []
67 | base_color = Color(swatch=swatch)
68 | colors.append(base_color)
69 | if swatch.name == "white":
70 | for shade in WHITE_SHADES:
71 | color = Color(swatch=swatch, shade=shade)
72 | colors.append(color)
73 | return colors
74 | if swatch.name == "black":
75 | for shade in BLACK_SHADES:
76 | color = Color(swatch=swatch, shade=shade)
77 | colors.append(color)
78 | return colors
79 | for shade in SHADES:
80 | color = Color(swatch=swatch, shade=shade)
81 | colors.append(color)
82 | if swatch.accent:
83 | for shade in ACCENT_SHADES:
84 | color = Color(swatch=swatch, shade=shade, accent=True)
85 | colors.append(color)
86 | return colors
87 |
88 | def color_clicked(e):
89 | self.color = e.control.bgcolor
90 | print(self.color)
91 |
92 | for swatch in swatches:
93 | swatch_colors = ft.Column(spacing=1, controls=[])
94 | for color in generate_color_names(swatch):
95 | swatch_colors.controls.append(
96 | ft.Container(
97 | height=20,
98 | width=20,
99 | border_radius=20,
100 | bgcolor=color.name,
101 | on_click=color_clicked,
102 | )
103 | )
104 | self.controls.append(swatch_colors)
105 |
106 |
107 | def main(page: ft.Page):
108 | color_picker = PaletteColorPicker()
109 |
110 | def dialog_closed(e):
111 | text_icon.icon_color = e.control.content.color
112 | text_icon.update()
113 |
114 | # color_picker = PaletteColorPicker()
115 | d = ft.AlertDialog(content=color_picker, on_dismiss=dialog_closed)
116 |
117 | page.dialog = d
118 |
119 | def open_color_picker(e):
120 | d.open = True
121 | page.update()
122 |
123 | text_icon = ft.IconButton(
124 | icon=ft.icons.FORMAT_COLOR_TEXT, on_click=open_color_picker
125 | )
126 |
127 | page.add(text_icon)
128 |
129 |
130 | ft.app(target=main)
131 |
--------------------------------------------------------------------------------
/test_projects/test.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 |
4 | def main(page: ft.Page):
5 | def dropdown_changed(e):
6 | dd.value = ""
7 | dd.update()
8 |
9 | dd = ft.Dropdown(
10 | width=100,
11 | options=[
12 | ft.dropdown.Option("Red"),
13 | ft.dropdown.Option("Green"),
14 | ft.dropdown.Option("Blue"),
15 | ],
16 | on_change=dropdown_changed,
17 | )
18 | page.add(dd)
19 |
20 |
21 | ft.app(target=main)
22 |
--------------------------------------------------------------------------------
/test_projects/test_draggable_divider.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | def main(page: ft.Page):
4 | page.title = "Draggable Divider example"
5 |
6 | def move_divider(e: ft.DragUpdateEvent):
7 | c.height += e.delta_y
8 | c.update()
9 |
10 | c = ft.Container(
11 | bgcolor=ft.colors.AMBER,
12 | alignment=ft.alignment.center,
13 | height=100,
14 | #expand=1,
15 | )
16 |
17 | page.add(ft.Column(
18 | [
19 | c,
20 | ft.GestureDetector(
21 | content=ft.Divider(),
22 | on_pan_update=move_divider),
23 | ft.Container(bgcolor=ft.colors.PINK, alignment=ft.alignment.center, expand=1),],
24 | spacing=0,
25 | width=400,
26 | height=400))
27 |
28 | ft.app(target=main)
29 |
--------------------------------------------------------------------------------
/test_projects/test_draggable_vertical_divider.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | def main(page: ft.Page):
4 | page.title = "Draggable VerticalDivider example"
5 |
6 | def move_vertical_divider(e: ft.DragUpdateEvent):
7 | c.width += e.delta_x
8 | page.update()
9 |
10 | c = ft.Container(
11 | bgcolor=ft.colors.ORANGE_300,
12 | alignment=ft.alignment.center,
13 | width=100,
14 | #expand=1,
15 | )
16 | page.add(ft.Row(controls=
17 | [
18 | c,
19 | ft.GestureDetector(
20 | content=ft.VerticalDivider(),
21 | on_pan_update=move_vertical_divider),
22 | ft.Container(
23 | bgcolor=ft.colors.BROWN_400,
24 | alignment=ft.alignment.center,
25 | expand=1,
26 | ),
27 | ],
28 | spacing=0,
29 | width=400,
30 | height=400
31 | )
32 | )
33 | ft.app(target=main)
34 |
--------------------------------------------------------------------------------
/test_projects/test_gradient.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | import colorsys
3 |
4 |
5 | def rgb2hex(rgb):
6 | return "#{:02x}{:02x}{:02x}".format(
7 | int(rgb[0] * 255.0), int(rgb[1] * 255.0), int(rgb[2] * 255.0)
8 | )
9 |
10 |
11 | def generate_hues(number_of_hues):
12 | colors = []
13 | for i in range(0, number_of_hues + 1):
14 | color = rgb2hex(colorsys.hsv_to_rgb(i / number_of_hues, 1, 1))
15 | colors.append(color)
16 | return colors
17 |
18 |
19 | def generate_s(number_of_s):
20 | colors = []
21 | for i in range(0, number_of_s + 1):
22 | color = rgb2hex(colorsys.hsv_to_rgb(0.5, i / number_of_s, 1))
23 | colors.append(color)
24 | return colors
25 |
26 |
27 | def generate_v(number_of_v):
28 | colors = []
29 | for i in range(0, number_of_v + 1):
30 | color = rgb2hex(colorsys.hsv_to_rgb(1, 0, (number_of_v - i) / number_of_v))
31 | colors.append(color)
32 | return colors
33 |
34 |
35 | def main(page: ft.Page):
36 | c_h = ft.Container(
37 | gradient=ft.LinearGradient(
38 | begin=ft.alignment.center_left,
39 | end=ft.alignment.center_right,
40 | colors=generate_hues(10),
41 | ),
42 | width=150,
43 | height=30,
44 | border_radius=5,
45 | )
46 |
47 | c_s = ft.Container(
48 | gradient=ft.LinearGradient(
49 | begin=ft.alignment.center_left,
50 | end=ft.alignment.center_right,
51 | colors=generate_s(2),
52 | ),
53 | width=300,
54 | height=150,
55 | border_radius=5,
56 | )
57 |
58 | c_v = ft.Container(
59 | gradient=ft.LinearGradient(
60 | begin=ft.alignment.top_center,
61 | end=ft.alignment.bottom_center,
62 | colors=["#00ffffff", "#ff000000"],
63 | ),
64 | width=300,
65 | height=150,
66 | border_radius=5,
67 | )
68 |
69 | stack = ft.Stack(controls=[c_s, c_v])
70 |
71 | shader_mask_on_s = ft.ShaderMask(
72 | content=c_s,
73 | blend_mode=ft.BlendMode.MULTIPLY,
74 | shader=ft.LinearGradient(
75 | begin=ft.alignment.top_center,
76 | end=ft.alignment.bottom_center,
77 | colors=[ft.colors.WHITE, ft.colors.BLACK],
78 | # stops=[0.5, 1.0],
79 | ),
80 | border_radius=10,
81 | )
82 |
83 | shader_mask_on_v = ft.ShaderMask(
84 | content=c_v,
85 | blend_mode=ft.BlendMode.SATURATION,
86 | shader=ft.LinearGradient(
87 | begin=ft.alignment.center_left,
88 | end=ft.alignment.center_right,
89 | colors=generate_s,
90 | stops=[0.5, 1.0],
91 | ),
92 | border_radius=10,
93 | )
94 |
95 | page.add(stack, shader_mask_on_s)
96 |
97 |
98 | ft.app(target=main)
99 |
--------------------------------------------------------------------------------
/test_projects/test_route2.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 |
3 | def main(page: ft.Page):
4 | page.add(ft.Text(f"Initial route: {page.route}"))
5 |
6 | def route_change(e):
7 | page.add(ft.Text(f"New route: {e.route}"))
8 | print(len(page.views))
9 |
10 | def go_store(e):
11 | page.route = "/store"
12 | page.update()
13 |
14 | page.on_route_change = route_change
15 | page.add(ft.ElevatedButton("Go to Store", on_click=go_store))
16 | print(len(page.views))
17 |
18 | ft.app(target=main, view=ft.WEB_BROWSER)
--------------------------------------------------------------------------------
/test_projects/test_routing.py:
--------------------------------------------------------------------------------
1 | import flet
2 | from flet import AppBar, ElevatedButton, Page, Text, View, colors
3 |
4 |
5 | def main(page: Page):
6 | page.title = "Routes Example"
7 |
8 | print("Initial route:", page.route)
9 |
10 | def route_change(e):
11 | print("Route change:", e.route)
12 | page.views.clear()
13 | page.views.append(
14 | View(
15 | "/",
16 | [
17 | AppBar(title=Text("Flet app")),
18 | ElevatedButton("Go to settings", on_click=open_settings),
19 | ],
20 | )
21 | )
22 | if page.route == "/settings" or page.route == "/settings/mail":
23 | page.views.append(
24 | View(
25 | "/settings",
26 | [
27 | AppBar(title=Text("Settings"), bgcolor=colors.SURFACE_VARIANT),
28 | Text("Settings!", style="bodyMedium"),
29 | ElevatedButton(
30 | "Go to mail settings", on_click=open_mail_settings
31 | ),
32 | ],
33 | )
34 | )
35 | if page.route == "/settings/mail":
36 | page.views.append(
37 | View(
38 | "/settings/mail",
39 | [
40 | AppBar(
41 | title=Text("Mail Settings"), bgcolor=colors.SURFACE_VARIANT
42 | ),
43 | Text("Mail settings!"),
44 | ],
45 | )
46 | )
47 | page.update()
48 |
49 | def view_pop(e):
50 | print("View pop:", e.view)
51 | page.views.pop()
52 | top_view = page.views[-1]
53 | page.go(top_view.route)
54 |
55 | page.on_route_change = route_change
56 | page.on_view_pop = view_pop
57 |
58 | def open_mail_settings(e):
59 | page.go("/settings/mail")
60 |
61 | def open_settings(e):
62 | page.go("/settings")
63 |
64 | page.go(page.route)
65 |
66 |
67 | flet.app(target=main, view=flet.WEB_BROWSER)
--------------------------------------------------------------------------------