├── .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 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final-part1/images/Ace_clubs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final-part1/images/Ace_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 88 | 93 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final-part1/images/Ace_hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final-part1/images/Ace_spades.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 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 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final/images/4_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 122 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final/images/Ace_clubs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final/images/Ace_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 88 | 93 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final/images/Ace_hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-final/images/Ace_spades.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 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 | 16 | 27 | 29 | 34 | 38 | 39 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 55 | 57 | 61 | 69 | 70 | 74 | 82 | 83 | 84 | 85 | 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 | 16 | 27 | 29 | 34 | 38 | 39 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 55 | 57 | 61 | 69 | 70 | 74 | 82 | 83 | 84 | 85 | 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 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-rules/images/4_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 122 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-rules/images/Ace_clubs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-rules/images/Ace_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 88 | 93 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-rules/images/Ace_hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-rules/images/Ace_spades.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 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 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-setup/images/4_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 116 | 122 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-setup/images/Ace_clubs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-setup/images/Ace_diamonds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 88 | 93 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-setup/images/Ace_hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /flet-solitaire/solitaire-game-setup/images/Ace_spades.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 44 | 55 | 56 | 58 | 59 | 61 | image/svg+xml 62 | 64 | 65 | 66 | 67 | 68 | 70 | 73 | 82 | 87 | 92 | 98 | 104 | 110 | 111 | 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 | 14 | 25 | 27 | 32 | 45 | 49 | 53 | 57 | 61 | 65 | 66 | 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 | 14 | 25 | 27 | 32 | 36 | 37 | 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) --------------------------------------------------------------------------------