├── README.md ├── cookiecutter.json ├── hooks └── post_gen_project.py └── {{cookiecutter.project_slug}} ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── demo ├── customTkinter_ │ ├── complex_example.py │ └── sample.mp4 ├── kivy_ │ ├── README.txt │ ├── android.txt │ ├── data │ │ ├── background.png │ │ ├── faust_github.jpg │ │ ├── icons │ │ │ ├── README │ │ │ ├── bug.png │ │ │ ├── chevron-left.png │ │ │ └── chevron-right.png │ │ └── screens │ │ │ ├── accordions.kv │ │ │ ├── bubbles.kv │ │ │ ├── buttons.kv │ │ │ ├── carousel.kv │ │ │ ├── checkboxes.kv │ │ │ ├── codeinput.kv │ │ │ ├── dropdown.kv │ │ │ ├── filechoosers.kv │ │ │ ├── popups.kv │ │ │ ├── progressbar.kv │ │ │ ├── rstdocument.kv │ │ │ ├── scatter.kv │ │ │ ├── screenmanager.kv │ │ │ ├── sliders.kv │ │ │ ├── spinner.kv │ │ │ ├── splitter.kv │ │ │ ├── switches.kv │ │ │ ├── tabbedpanel + layouts.kv │ │ │ ├── textinputs.kv │ │ │ └── togglebutton.kv │ ├── main.py │ ├── requirements.txt │ ├── sample.gif │ └── showcase.kv ├── pysimplegui_ │ ├── LICENSE │ ├── README.md │ ├── SimpleGUIBuilder.gif │ ├── layout_parser.py │ ├── requirements.txt │ ├── simple_gui_builder.py │ └── tree_node.py ├── tkinter_ │ ├── README.md │ ├── icons │ │ ├── align-center.png │ │ ├── align-justify.png │ │ ├── align-left.png │ │ ├── align-right.png │ │ ├── bold.png │ │ ├── copy.png │ │ ├── cut.png │ │ ├── find.png │ │ ├── font-color.png │ │ ├── highlight.png │ │ ├── italic.png │ │ ├── new.png │ │ ├── open.png │ │ ├── paste.png │ │ ├── redo.png │ │ ├── save.png │ │ ├── strike.png │ │ ├── underline.png │ │ └── undo.png │ ├── main.py │ ├── requirements.txt │ └── text-editor.png ├── toga_ │ ├── sample.gif │ └── togablogpost │ │ ├── .gitignore │ │ ├── CHANGELOG │ │ ├── LICENSE │ │ ├── README.rst │ │ ├── pyproject.toml │ │ ├── src │ │ └── togablogpost │ │ │ ├── __init__.py │ │ │ ├── __main__.py │ │ │ ├── app.py │ │ │ ├── english_words.txt │ │ │ ├── hangman_images │ │ │ ├── death.PNG │ │ │ ├── hangman-0.PNG │ │ │ ├── hangman-1.PNG │ │ │ ├── hangman-2.PNG │ │ │ ├── hangman-3.PNG │ │ │ ├── hangman-4.PNG │ │ │ ├── hangman-5.PNG │ │ │ ├── hangman-6.PNG │ │ │ └── victory.PNG │ │ │ └── resources │ │ │ ├── __init__.py │ │ │ ├── togablogpost.icns │ │ │ ├── togablogpost.ico │ │ │ └── togablogpost.png │ │ └── tests │ │ ├── __init__.py │ │ ├── test_app.py │ │ └── togablogpost.py ├── wxPython_ │ ├── README.md │ ├── demo.gif │ ├── main.py │ └── requirements.txt └── {{cookiecutter.gui_framework}} │ ├── README.md │ ├── generator.py │ ├── main.py │ ├── main_window.ui │ ├── my_theme.xml │ └── requirements.txt ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── pyproject.toml ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests └── __init__.py ├── tox.ini └── {{cookiecutter.project_slug}} ├── __init__.py └── {{cookiecutter.project_slug}}.py /README.md: -------------------------------------------------------------------------------- 1 | Cookiecutter Python GUI Application 2 | =================================== 3 | 4 | Cookiecutter template for Python GUI Applications based on cookiecutter-pypackage: https://github.com/audreyfeldroy/cookiecutter-pypackage. 5 | 6 | This template is mostly intended for beginners in GUI development using Python, but it can be used by experienced developers also. 7 | 8 | Besides providing the template and the needed tools for GUI development, this repository also comes with demos for most used python GUI frameworks, in order to get you started, if you are a beginner. 9 | 10 | Features 11 | ----- 12 | * Free software: BSD license 13 | * Dependency tracking using [poetry](https://python-poetry.org/ "poetry") 14 | * [Pytest](http://pytest.org/ "Pytest") runner: Supports `unittest`, `pytest`, `nose` style tests and more 15 | * [Travis-CI](http://travis-ci.org/"Travis-CI"): Ready for Travis Continuous integration testing 16 | * [Tox](http://testrun.org/tox/ "Tox") testing: Setup to easily test for python 2.6, 2.7, 3.3 and PyPy_ 17 | * [Sphinx](http://sphinx-doc.org/ "Sphinx") docs: Documentation ready for generation with, for example, [ReadTheDocs](https://readthedocs.org/ "ReadTheDocs") 18 | * [Wheel](http://pythonwheels.com "Wheel") support: Use the newest python package distribution standard from the get go 19 | 20 | 21 | Supported GUI Frameworks 22 | ----- 23 | * [PyQt5 and PyQt6](https://www.riverbankcomputing.com/static/Docs/PyQt6 "PyQt5 and PyQt6") 24 | * [PySide2 and PySide6](https://doc.qt.io/qtforpython "PySide2 and PySide6") 25 | * [tkinter](https://docs.python.org/3/library/tk.html "tkinter") and [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter) 26 | * TODO: Add [DearPyGui](https://github.com/hoffstadt/DearPyGui) 27 | * [PySimpleGUI](https://www.pysimplegui.org/en/latest/ "PySimpleGUI") 28 | * [wxPython](https://www.wxpython.org/ "wxPython") 29 | * TODO: Add [DelphiFMX4Python](https://pypi.org/project/delphifmx/) and [DelphiVCL](https://pypi.org/project/delphivcl/) 30 | * [Toga](https://github.com/beeware/toga "Toga") 31 | * TODO: Add [PyForms](https://github.com/UmSenhorQualquer/pyforms-gui) 32 | * [Kivy](https://kivy.org/doc/stable "Kivy") 33 | * TODO: Add [libavg](https://www.libavg.de/site/projects/libavg/wiki/ReleaseInstall) 34 | 35 | Demos: 36 | ----- 37 | * Qt family (PyQt5, PyQt6, PySide2, PySide6): 38 | 39 | ![Qt Demo](https://github.com/UN-GCPDS/qt-material/raw/master/docs/source/notebooks/_images/dark.gif) 40 | 41 | 42 | * Tkinter: 43 | 44 | ![Text Editor](https://github.com/MihailCosmin/cookiecutter-python-gui-application/blob/main/%7B%7Bcookiecutter.project_slug%7D%7D/demo/tkinter_/text-editor.png) 45 | 46 | * CustomTkinter: 47 | 48 | https://github.com/MihailCosmin/cookiecutter-python-gui-application/assets/14019626/84625eb8-f643-47bc-b8e4-b7c6adc1fd06 49 | 50 | * PySimpleGUI: 51 | 52 | ![PySimpleGUI](https://github.com/PriestTheBeast/SimpleGUIBuilder/blob/main/SimpleGUIBuilder.gif) 53 | 54 | * wxPython: 55 | 56 | ![Web Browser](https://github.com/MihailCosmin/cookiecutter-python-gui-application/blob/main/%7B%7Bcookiecutter.project_slug%7D%7D/demo/wxPython_/demo.gif) 57 | 58 | 59 | * [Toga](https://github.com/Depot-Analytics/toga-hangman): 60 | 61 | ![Toga](https://github.com/MihailCosmin/cookiecutter-python-gui-application/blob/2461902454a4803171acadd12d4c2f50e24ee5d5/%7B%7Bcookiecutter.project_slug%7D%7D/demo/toga_/sample.gif) 62 | 63 | * Kivy: 64 | 65 | ![Kivy](https://github.com/MihailCosmin/cookiecutter-python-gui-application/blob/main/%7B%7Bcookiecutter.project_slug%7D%7D/demo/kivy_/sample.gif) 66 | 67 | 68 | Frameworks Comparison 69 | ----- 70 | TODO: Add comparison table, main features 71 | 72 | Usage 73 | ----- 74 | 75 | Generate a Python GUI Application project: 76 | 77 | cookiecutter https://github.com/MihailCosmin/cookiecutter-python-gui-application.git 78 | 79 | Not Exactly What You Want? 80 | -------------------------- 81 | 82 | Don't worry, you have options: 83 | 84 | Similar Cookiecutter Templates 85 | ------------------------------ 86 | 87 | * [audreyr/cookiecutter-pypackage](https://github.com/audreyfeldroy/cookiecutter-pypackage "audreyr/cookiecutter-pypackage"): The original pypackage. 88 | 89 | Fork This 90 | --------- 91 | 92 | If you have differences in your preferred setup, I encourage you to fork this 93 | to create your own version. Once you have your fork working, add it to the 94 | Similar Cookiecutter Templates list with a brief explanation. It's up to you 95 | whether or not to rename your fork. 96 | 97 | Or Submit a Pull Request 98 | ------------------------ 99 | 100 | I also accept pull requests on this, if they're small, atomic, and if they 101 | make my own packaging experience better. 102 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "Cosmin Munteanu", 3 | "email": "cosmin@munteanu.com", 4 | "github_username": "MihailCosmin", 5 | "project_name": "Python GUI Application", 6 | "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", 7 | "project_short_description": "Python template that contains all the things you need to create a Python GUI Application.", 8 | "pypi_username": "{{ cookiecutter.github_username }}", 9 | "version": "0.1.0", 10 | "gui_framework": ["PyQt5", "PyQt6", "PySide2", "PySide6", "Tkinter", "customTkinter", "PySimpleGUI", "wxPython", "Toga", "Kivy", "None - Will decide later"], 11 | "use_poetry": "y", 12 | "use_pytest": "y", 13 | "use_pypi_deployment_with_travis": "y", 14 | "add_pyup_badge": "y", 15 | "create_author_file": "y", 16 | "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"], 17 | "keep_demo": "y" 18 | } 19 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | # cat post_gen_project.py 2 | import os 3 | import shutil 4 | 5 | print(os.getcwd()) # prints /absolute/path/to/{{cookiecutter.project_slug}} 6 | 7 | def remove(filepath: str): 8 | """Remove a file or a directory. 9 | 10 | Args: 11 | filepath (str): Path to the file or directory to remove. 12 | """ 13 | if os.path.isfile(filepath): 14 | os.remove(filepath) 15 | elif os.path.isdir(filepath): 16 | shutil.rmtree(filepath) 17 | 18 | use_poetry = '{{cookiecutter.use_poetry}}' == 'y' 19 | keep_demo = '{{cookiecutter.keep_demo}}' == 'y' 20 | 21 | if not use_poetry: 22 | # remove top-level file inside the generated folder 23 | remove('pyproject.toml') 24 | 25 | if not keep_demo: 26 | # remove top-level file inside the generated folder 27 | remove('demo') 28 | else: 29 | if '{{cookiecutter.gui_framework}}'.startswith('PySide') or '{{cookiecutter.gui_framework}}'.startswith('PyQt'): 30 | remove('demo/toga_') 31 | remove('demo/kivy_') 32 | remove('demo/tkinter_') 33 | remove('demo/wxPython_') 34 | remove('demo/pysimplegui_') 35 | remove('demo/customTkinter_') 36 | 37 | if '{{cookiecutter.gui_framework}}' == 'Tkinter': 38 | remove('demo/Tkinter') 39 | os.rename('demo/tkinter_', 'demo/tkinter') 40 | remove('demo/toga_') 41 | remove('demo/kivy_') 42 | remove('demo/wxPython_') 43 | remove('demo/pysimplegui_') 44 | remove('demo/customTkinter_') 45 | 46 | if '{{cookiecutter.gui_framework}}' == 'customTkinter': 47 | remove('demo/customTkinter') 48 | os.rename('demo/customTkinter_', 'demo/customTkinter') 49 | remove('demo/toga_') 50 | remove('demo/kivy_') 51 | remove('demo/tkinter_') 52 | remove('demo/wxPython_') 53 | remove('demo/pysimplegui_') 54 | 55 | if '{{cookiecutter.gui_framework}}' == 'Kivy': 56 | remove('demo/Kivy') 57 | os.rename('demo/kivy_', 'demo/kivy') 58 | remove('demo/toga_') 59 | remove('demo/tkinter_') 60 | remove('demo/wxPython_') 61 | remove('demo/pysimplegui_') 62 | remove('demo/customTkinter_') 63 | 64 | if '{{cookiecutter.gui_framework}}' == 'wxPython': 65 | remove('demo/wxPython') 66 | os.rename('demo/wxPython_', 'demo/wxPython') 67 | remove('demo/toga_') 68 | remove('demo/kivy_') 69 | remove('demo/tkinter_') 70 | remove('demo/pysimplegui_') 71 | remove('demo/customTkinter_') 72 | 73 | if '{{cookiecutter.gui_framework}}' == 'PySimpleGUI': 74 | remove('demo/PySimpleGUI') 75 | os.rename('demo/pysimplegui_', 'demo/PySimpleGUI') 76 | remove('demo/toga_') 77 | remove('demo/kivy_') 78 | remove('demo/tkinter_') 79 | remove('demo/wxPython_') 80 | remove('demo/customTkinter_') 81 | 82 | if '{{cookiecutter.gui_framework}}' == 'Toga': 83 | remove('demo/Toga') 84 | os.rename('demo/toga_', 'demo/Toga') 85 | remove('demo/kivy_') 86 | remove('demo/tkinter_') 87 | remove('demo/wxPython_') 88 | remove('demo/pysimplegui_') 89 | remove('demo/customTkinter_') 90 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * {{ cookiecutter.project_name }} version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.com 2 | 3 | language: python 4 | python: 5 | - 3.8 6 | - 3.7 7 | - 3.6 8 | 9 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 10 | install: pip install -U tox-travis 11 | 12 | # Command to run tests, e.g. python setup.py test 13 | script: tox 14 | 15 | {% if cookiecutter.use_pypi_deployment_with_travis == 'y' -%} 16 | # Assuming you have installed the travis-ci CLI tool, after you 17 | # create the Github repo and add it to Travis, run the 18 | # following command to finish PyPI deployment setup: 19 | # $ travis encrypt --add deploy.password 20 | deploy: 21 | provider: pypi 22 | distributions: sdist bdist_wheel 23 | user: {{ cookiecutter.pypi_username }} 24 | password: 25 | secure: PLEASE_REPLACE_ME 26 | on: 27 | tags: true 28 | repo: {{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 29 | python: 3.8 30 | {%- endif %} 31 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * {{ cookiecutter.full_name }} <{{ cookiecutter.email }}> 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | {{ cookiecutter.project_name }} could always use more documentation, whether as part of the 42 | official {{ cookiecutter.project_name }} docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `{{ cookiecutter.project_slug }}` for local development. 61 | 62 | 1. Fork the `{{ cookiecutter.project_slug }}` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/{{ cookiecutter.project_slug }}.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv {{ cookiecutter.project_slug }} 70 | $ cd {{ cookiecutter.project_slug }}/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 {{ cookiecutter.project_slug }} tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | {% if cookiecutter.use_pytest == 'y' -%} 115 | $ pytest tests.test_{{ cookiecutter.project_slug }} 116 | {% else %} 117 | $ python -m unittest tests.test_{{ cookiecutter.project_slug }} 118 | {%- endif %} 119 | 120 | Deploying 121 | --------- 122 | 123 | A reminder for the maintainers on how to deploy. 124 | Make sure all your changes are committed (including an entry in HISTORY.rst). 125 | Then run:: 126 | 127 | $ bump2version patch # possible: major / minor / patch 128 | $ git push 129 | $ git push --tags 130 | 131 | Travis will then deploy to PyPI if tests pass. 132 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | {{ cookiecutter.version }} ({% now 'local' %}) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/LICENSE: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.open_source_license == 'MIT license' -%} 2 | MIT License 3 | 4 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | {% elif cookiecutter.open_source_license == 'BSD license' %} 24 | 25 | BSD License 26 | 27 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 28 | All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without modification, 31 | are permitted provided that the following conditions are met: 32 | 33 | * Redistributions of source code must retain the above copyright notice, this 34 | list of conditions and the following disclaimer. 35 | 36 | * Redistributions in binary form must reproduce the above copyright notice, this 37 | list of conditions and the following disclaimer in the documentation and/or 38 | other materials provided with the distribution. 39 | 40 | * Neither the name of the copyright holder nor the names of its 41 | contributors may be used to endorse or promote products derived from this 42 | software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 45 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 46 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 47 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 48 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 49 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 51 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 52 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 53 | OF THE POSSIBILITY OF SUCH DAMAGE. 54 | {% elif cookiecutter.open_source_license == 'ISC license' -%} 55 | ISC License 56 | 57 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 58 | 59 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 62 | {% elif cookiecutter.open_source_license == 'Apache Software License 2.0' -%} 63 | Apache Software License 2.0 64 | 65 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | {% elif cookiecutter.open_source_license == 'GNU General Public License v3' -%} 79 | GNU GENERAL PUBLIC LICENSE 80 | Version 3, 29 June 2007 81 | 82 | {{ cookiecutter.project_short_description }} 83 | Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.full_name }} 84 | 85 | This program is free software: you can redistribute it and/or modify 86 | it under the terms of the GNU General Public License as published by 87 | the Free Software Foundation, either version 3 of the License, or 88 | (at your option) any later version. 89 | 90 | This program is distributed in the hope that it will be useful, 91 | but WITHOUT ANY WARRANTY; without even the implied warranty of 92 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 93 | GNU General Public License for more details. 94 | 95 | You should have received a copy of the GNU General Public License 96 | along with this program. If not, see . 97 | 98 | Also add information on how to contact you by electronic and paper mail. 99 | 100 | You should also get your employer (if you work as a programmer) or school, 101 | if any, to sign a "copyright disclaimer" for the program, if necessary. 102 | For more information on this, and how to apply and follow the GNU GPL, see 103 | . 104 | 105 | The GNU General Public License does not permit incorporating your program 106 | into proprietary programs. If your program is a subroutine library, you 107 | may consider it more useful to permit linking proprietary applications with 108 | the library. If this is what you want to do, use the GNU Lesser General 109 | Public License instead of this License. But first, please read 110 | . 111 | {% endif %} 112 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/MANIFEST.in: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.create_author_file == 'y' -%} 2 | include AUTHORS.rst 3 | {% endif -%} 4 | include CONTRIBUTING.rst 5 | include HISTORY.rst 6 | include LICENSE 7 | include README.rst 8 | 9 | recursive-include tests * 10 | recursive-exclude * __pycache__ 11 | recursive-exclude * *.py[co] 12 | 13 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint: ## check style with flake8 51 | flake8 {{ cookiecutter.project_slug }} tests 52 | 53 | test: ## run tests quickly with the default Python 54 | {%- if cookiecutter.use_pytest == 'y' %} 55 | pytest 56 | {%- else %} 57 | python setup.py test 58 | {%- endif %} 59 | 60 | test-all: ## run tests on every Python version with tox 61 | tox 62 | 63 | coverage: ## check code coverage quickly with the default Python 64 | {%- if cookiecutter.use_pytest == 'y' %} 65 | coverage run --source {{ cookiecutter.project_slug }} -m pytest 66 | {%- else %} 67 | coverage run --source {{ cookiecutter.project_slug }} setup.py test 68 | {%- endif %} 69 | coverage report -m 70 | coverage html 71 | $(BROWSER) htmlcov/index.html 72 | 73 | docs: ## generate Sphinx HTML documentation, including API docs 74 | rm -f docs/{{ cookiecutter.project_slug }}.rst 75 | rm -f docs/modules.rst 76 | sphinx-apidoc -o docs/ {{ cookiecutter.project_slug }} 77 | $(MAKE) -C docs clean 78 | $(MAKE) -C docs html 79 | $(BROWSER) docs/_build/html/index.html 80 | 81 | servedocs: docs ## compile the docs watching for changes 82 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 83 | 84 | release: dist ## package and upload a release 85 | twine upload dist/* 86 | 87 | dist: clean ## builds source and wheel package 88 | python setup.py sdist 89 | python setup.py bdist_wheel 90 | ls -l dist 91 | 92 | install: clean ## install the package to the active Python's site-packages 93 | python setup.py install 94 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.rst: -------------------------------------------------------------------------------- 1 | {% set is_open_source = cookiecutter.open_source_license != 'Not open source' -%} 2 | {% for _ in cookiecutter.project_name %}={% endfor %} 3 | {{ cookiecutter.project_name }} 4 | {% for _ in cookiecutter.project_name %}={% endfor %} 5 | 6 | {% if is_open_source %} 7 | .. image:: https://img.shields.io/pypi/v/{{ cookiecutter.project_slug }}.svg 8 | :target: https://pypi.python.org/pypi/{{ cookiecutter.project_slug }} 9 | 10 | .. image:: https://img.shields.io/travis/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}.svg 11 | :target: https://travis-ci.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 12 | 13 | .. image:: https://readthedocs.org/projects/{{ cookiecutter.project_slug | replace("_", "-") }}/badge/?version=latest 14 | :target: https://{{ cookiecutter.project_slug | replace("_", "-") }}.readthedocs.io/en/latest/?version=latest 15 | :alt: Documentation Status 16 | {%- endif %} 17 | 18 | {% if cookiecutter.add_pyup_badge == 'y' %} 19 | .. image:: https://pyup.io/repos/github/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/shield.svg 20 | :target: https://pyup.io/repos/github/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/ 21 | :alt: Updates 22 | {% endif %} 23 | 24 | 25 | {{ cookiecutter.project_short_description }} 26 | 27 | {% if is_open_source %} 28 | * Free software: {{ cookiecutter.open_source_license }} 29 | * Documentation: https://{{ cookiecutter.project_slug | replace("_", "-") }}.readthedocs.io. 30 | {% endif %} 31 | 32 | Features 33 | -------- 34 | 35 | * TODO 36 | 37 | Credits 38 | ------- 39 | 40 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 41 | 42 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 43 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 44 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/customTkinter_/complex_example.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | import tkinter.messagebox 3 | import customtkinter 4 | 5 | customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light" 6 | customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" 7 | 8 | 9 | class App(customtkinter.CTk): 10 | def __init__(self): 11 | super().__init__() 12 | 13 | # configure window 14 | self.title("CustomTkinter complex_example.py") 15 | self.geometry(f"{1100}x{580}") 16 | 17 | # configure grid layout (4x4) 18 | self.grid_columnconfigure(1, weight=1) 19 | self.grid_columnconfigure((2, 3), weight=0) 20 | self.grid_rowconfigure((0, 1, 2), weight=1) 21 | 22 | # create sidebar frame with widgets 23 | self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0) 24 | self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew") 25 | self.sidebar_frame.grid_rowconfigure(4, weight=1) 26 | self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CustomTkinter", font=customtkinter.CTkFont(size=20, weight="bold")) 27 | self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10)) 28 | self.sidebar_button_1 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event) 29 | self.sidebar_button_1.grid(row=1, column=0, padx=20, pady=10) 30 | self.sidebar_button_2 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event) 31 | self.sidebar_button_2.grid(row=2, column=0, padx=20, pady=10) 32 | self.sidebar_button_3 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event) 33 | self.sidebar_button_3.grid(row=3, column=0, padx=20, pady=10) 34 | self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w") 35 | self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0)) 36 | self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"], 37 | command=self.change_appearance_mode_event) 38 | self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10)) 39 | self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w") 40 | self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0)) 41 | self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"], 42 | command=self.change_scaling_event) 43 | self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20)) 44 | 45 | # create main entry and button 46 | self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") 47 | self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew") 48 | 49 | self.main_button_1 = customtkinter.CTkButton(master=self, fg_color="transparent", border_width=2, text_color=("gray10", "#DCE4EE")) 50 | self.main_button_1.grid(row=3, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew") 51 | 52 | # create textbox 53 | self.textbox = customtkinter.CTkTextbox(self, width=250) 54 | self.textbox.grid(row=0, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew") 55 | 56 | # create tabview 57 | self.tabview = customtkinter.CTkTabview(self, width=250) 58 | self.tabview.grid(row=0, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew") 59 | self.tabview.add("CTkTabview") 60 | self.tabview.add("Tab 2") 61 | self.tabview.add("Tab 3") 62 | self.tabview.tab("CTkTabview").grid_columnconfigure(0, weight=1) # configure grid of individual tabs 63 | self.tabview.tab("Tab 2").grid_columnconfigure(0, weight=1) 64 | 65 | self.optionmenu_1 = customtkinter.CTkOptionMenu(self.tabview.tab("CTkTabview"), dynamic_resizing=False, 66 | values=["Value 1", "Value 2", "Value Long Long Long"]) 67 | self.optionmenu_1.grid(row=0, column=0, padx=20, pady=(20, 10)) 68 | self.combobox_1 = customtkinter.CTkComboBox(self.tabview.tab("CTkTabview"), 69 | values=["Value 1", "Value 2", "Value Long....."]) 70 | self.combobox_1.grid(row=1, column=0, padx=20, pady=(10, 10)) 71 | self.string_input_button = customtkinter.CTkButton(self.tabview.tab("CTkTabview"), text="Open CTkInputDialog", 72 | command=self.open_input_dialog_event) 73 | self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10)) 74 | self.label_tab_2 = customtkinter.CTkLabel(self.tabview.tab("Tab 2"), text="CTkLabel on Tab 2") 75 | self.label_tab_2.grid(row=0, column=0, padx=20, pady=20) 76 | 77 | # create radiobutton frame 78 | self.radiobutton_frame = customtkinter.CTkFrame(self) 79 | self.radiobutton_frame.grid(row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") 80 | self.radio_var = tkinter.IntVar(value=0) 81 | self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:") 82 | self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") 83 | self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0) 84 | self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n") 85 | self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1) 86 | self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n") 87 | self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2) 88 | self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") 89 | 90 | # create slider and progressbar frame 91 | self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color="transparent") 92 | self.slider_progressbar_frame.grid(row=1, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew") 93 | self.slider_progressbar_frame.grid_columnconfigure(0, weight=1) 94 | self.slider_progressbar_frame.grid_rowconfigure(4, weight=1) 95 | self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame) 96 | self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 97 | self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) 98 | self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 99 | self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) 100 | self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 101 | self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4) 102 | self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 103 | self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical") 104 | self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns") 105 | self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical") 106 | self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns") 107 | 108 | # create scrollable frame 109 | self.scrollable_frame = customtkinter.CTkScrollableFrame(self, label_text="CTkScrollableFrame") 110 | self.scrollable_frame.grid(row=1, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew") 111 | self.scrollable_frame.grid_columnconfigure(0, weight=1) 112 | self.scrollable_frame_switches = [] 113 | for i in range(100): 114 | switch = customtkinter.CTkSwitch(master=self.scrollable_frame, text=f"CTkSwitch {i}") 115 | switch.grid(row=i, column=0, padx=10, pady=(0, 20)) 116 | self.scrollable_frame_switches.append(switch) 117 | 118 | # create checkbox and switch frame 119 | self.checkbox_slider_frame = customtkinter.CTkFrame(self) 120 | self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") 121 | self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) 122 | self.checkbox_1.grid(row=1, column=0, pady=(20, 0), padx=20, sticky="n") 123 | self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) 124 | self.checkbox_2.grid(row=2, column=0, pady=(20, 0), padx=20, sticky="n") 125 | self.checkbox_3 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) 126 | self.checkbox_3.grid(row=3, column=0, pady=20, padx=20, sticky="n") 127 | 128 | # set default values 129 | self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") 130 | self.checkbox_3.configure(state="disabled") 131 | self.checkbox_1.select() 132 | self.scrollable_frame_switches[0].select() 133 | self.scrollable_frame_switches[4].select() 134 | self.radio_button_3.configure(state="disabled") 135 | self.appearance_mode_optionemenu.set("Dark") 136 | self.scaling_optionemenu.set("100%") 137 | self.optionmenu_1.set("CTkOptionmenu") 138 | self.combobox_1.set("CTkComboBox") 139 | self.slider_1.configure(command=self.progressbar_2.set) 140 | self.slider_2.configure(command=self.progressbar_3.set) 141 | self.progressbar_1.configure(mode="indeterminnate") 142 | self.progressbar_1.start() 143 | self.textbox.insert("0.0", "CTkTextbox\n\n" + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20) 144 | self.seg_button_1.configure(values=["CTkSegmentedButton", "Value 2", "Value 3"]) 145 | self.seg_button_1.set("Value 2") 146 | 147 | def open_input_dialog_event(self): 148 | dialog = customtkinter.CTkInputDialog(text="Type in a number:", title="CTkInputDialog") 149 | print("CTkInputDialog:", dialog.get_input()) 150 | 151 | def change_appearance_mode_event(self, new_appearance_mode: str): 152 | customtkinter.set_appearance_mode(new_appearance_mode) 153 | 154 | def change_scaling_event(self, new_scaling: str): 155 | new_scaling_float = int(new_scaling.replace("%", "")) / 100 156 | customtkinter.set_widget_scaling(new_scaling_float) 157 | 158 | def sidebar_button_event(self): 159 | print("sidebar_button click") 160 | 161 | 162 | if __name__ == "__main__": 163 | app = App() 164 | app.mainloop() 165 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/customTkinter_/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/customTkinter_/sample.mp4 -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/README.txt: -------------------------------------------------------------------------------- 1 | How to Use: 2 | =========== 3 | 4 | * Create and activate a virtual environment: 5 | 6 | virtualenv venv 7 | 8 | venv\Scripts\Activate 9 | 10 | * Install dependencies: 11 | 12 | pip install -r requirements.txt 13 | 14 | * Run the GUI demo: 15 | 16 | python main.py 17 | 18 | 19 | Note: 20 | ---- 21 | This Demo is taken from the official Kivy repository: 22 | https://github.com/kivy/kivy 23 | 24 | 25 | Sample: 26 | ---- 27 | Kivy Showcase: 28 | 29 | ![Kivy](sample.gif) -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/android.txt: -------------------------------------------------------------------------------- 1 | title=Showcase 2 | author=Kivy team 3 | orientation=landscape 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/background.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/faust_github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/faust_github.jpg -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/icons/README: -------------------------------------------------------------------------------- 1 | Icons adapted from the Open Iconic set of icons, which are licensed under MIT. 2 | 3 | https://useiconic.com/ 4 | https://github.com/iconic/open-iconic 5 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/icons/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/bug.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-left.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-right.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/accordions.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Accordions' 3 | 4 | fullscreen: True 5 | 6 | BoxLayout: 7 | size_hint_y: None 8 | height: '48dp' 9 | 10 | ToggleButton: 11 | id: tbh 12 | text: 'Horizontal' 13 | group: 'accordion' 14 | state: 'down' 15 | 16 | ToggleButton: 17 | text: 'Vertical' 18 | group: 'accordion' 19 | 20 | Accordion: 21 | 22 | orientation: 'horizontal' if tbh.state == 'down' else 'vertical' 23 | 24 | AccordionItem: 25 | title: 'Panel 1' 26 | Label: 27 | text: 'This is a label fit to the content view' 28 | text_size: self.width, None 29 | 30 | AccordionItem: 31 | title: 'Panel 2' 32 | Button: 33 | text: 'A button, what else?' 34 | 35 | AccordionItem: 36 | title: 'Panel 3' 37 | Label: 38 | text: 'This is a label fit to the content view' 39 | text_size: self.width, None 40 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/bubbles.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Bubbles' 3 | 4 | Bubble: 5 | size_hint_y: None 6 | height: '48dp' 7 | 8 | BubbleButton: 9 | text: 'Cut' 10 | BubbleButton: 11 | text: 'Copy' 12 | BubbleButton: 13 | text: 'Paste' 14 | 15 | Widget: 16 | size_hint_y: None 17 | height: '48dp' 18 | 19 | BoxLayout: 20 | size_hint_y: None 21 | height: '48dp' 22 | Label: 23 | text: 'Hello' 24 | 25 | Bubble: 26 | arrow_pos: 'left_mid' 27 | Label: 28 | text: 'World' 29 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/buttons.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Buttons' 3 | 4 | Button: 5 | size_hint_y: None 6 | height: '48dp' 7 | text: 'Button normal' 8 | 9 | Button: 10 | size_hint_y: None 11 | height: '48dp' 12 | text: 'Button down' 13 | state: 'down' 14 | 15 | Button: 16 | size_hint_y: None 17 | height: '48dp' 18 | text: 'Button disabled' 19 | disabled: True 20 | 21 | Button: 22 | size_hint_y: None 23 | height: '48dp' 24 | text: 'Button down disabled' 25 | state: 'down' 26 | disabled: True 27 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/carousel.kv: -------------------------------------------------------------------------------- 1 | : 2 | font_size: '48sp' 3 | color: (.6, .6, .6, 1) 4 | canvas.before: 5 | Color: 6 | rgb: (.9, .9, .9) 7 | Rectangle: 8 | pos: self.x + sp(2), self.y + sp(2) 9 | size: self.width - sp(4), self.height - sp(4) 10 | 11 | ShowcaseScreen: 12 | name: 'Carousel' 13 | fullscreen: True 14 | 15 | BoxLayout: 16 | size_hint_y: None 17 | height: '48dp' 18 | 19 | ToggleButton: 20 | text: 'Loop' 21 | id: btnloop 22 | 23 | Label: 24 | size_hint_x: None 25 | width: self.height 26 | text: '{}'.format(carousel.index) 27 | 28 | Button: 29 | size_hint_x: None 30 | width: self.height 31 | text: 'Prev' 32 | on_release: carousel.load_previous() 33 | 34 | Button: 35 | size_hint_x: None 36 | width: self.height 37 | text: 'Next' 38 | on_release: carousel.load_next() 39 | 40 | Carousel: 41 | id: carousel 42 | loop: btnloop.state == 'down' 43 | 44 | ColoredLabel: 45 | text: 'Slide 0' 46 | 47 | ColoredLabel: 48 | text: 'Slide 1' 49 | 50 | ColoredLabel: 51 | text: 'Slide 2' 52 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/checkboxes.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'CheckBoxes' 3 | 4 | GridLayout: 5 | 6 | cols: 3 7 | spacing: '8dp' 8 | size_hint: .5, None 9 | height: self.minimum_height 10 | 11 | Label: 12 | text: 'Checkbox' 13 | 14 | CheckBox: 15 | size_hint_y: None 16 | height: '48dp' 17 | 18 | CheckBox: 19 | size_hint_y: None 20 | height: '48dp' 21 | 22 | Label: 23 | text: 'CheckBox with group' 24 | 25 | CheckBox: 26 | size_hint_y: None 27 | height: '48dp' 28 | group: 'g2' 29 | 30 | CheckBox: 31 | size_hint_y: None 32 | height: '48dp' 33 | group: 'g2' 34 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/codeinput.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | 3 | fullscreen: True 4 | name: 'CodeInput' 5 | 6 | CodeInput: 7 | padding: '4dp' 8 | text: 'class Hello(object):\n\tpass\n\nprint("Hello world")' 9 | focus: True if root.parent else False 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/dropdown.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | fullscreen: True 3 | name: 'DropDown' 4 | 5 | # trick to not lost the Dropdown instance 6 | # Dropdown itself is not really made to be used in kv. 7 | __safe_id: [dropdown.__self__] 8 | 9 | Button: 10 | id: btn 11 | text: '-' 12 | on_release: dropdown.open(self) 13 | size_hint_y: None 14 | height: '48dp' 15 | 16 | Widget: 17 | on_parent: dropdown.dismiss() 18 | 19 | DropDown: 20 | 21 | id: dropdown 22 | on_select: btn.text = 'Selected value: {}'.format(args[1]) 23 | 24 | Button: 25 | text: 'Value A' 26 | size_hint_y: None 27 | height: '48dp' 28 | on_release: dropdown.select('A') 29 | 30 | Button: 31 | text: 'Value B' 32 | size_hint_y: None 33 | height: '48dp' 34 | on_release: dropdown.select('B') 35 | 36 | Button: 37 | text: 'Value C' 38 | size_hint_y: None 39 | height: '48dp' 40 | on_release: dropdown.select('C') 41 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/filechoosers.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'FileChoosers' 3 | fullscreen: True 4 | 5 | BoxLayout: 6 | size_hint_y: None 7 | height: '48dp' 8 | 9 | ToggleButton: 10 | text: 'Icon' 11 | state: 'down' 12 | group: 'filechooser' 13 | on_release: filechooser.view_mode = 'icon' 14 | 15 | ToggleButton: 16 | text: 'List' 17 | group: 'filechooser' 18 | on_release: filechooser.view_mode = 'list' 19 | 20 | FileChooser: 21 | id: filechooser 22 | 23 | FileChooserIconLayout 24 | FileChooserListLayout 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/popups.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | popup: popup.__self__ 3 | fullscreen: True 4 | name: 'Popups' 5 | BoxLayout: 6 | id: bl 7 | Popup: 8 | id: popup 9 | title: "Hello World" 10 | on_parent: 11 | if self.parent == bl: self.parent.remove_widget(self) 12 | Button: 13 | text: 'press to dismiss' 14 | on_release: popup.dismiss() 15 | Button: 16 | text: 'press to show Popup' 17 | on_release: root.popup.open() 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/progressbar.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'ProgressBar' 3 | 4 | Label: 5 | text: 'Progression: {}%'.format(int(pb.value)) 6 | size_hint_y: None 7 | height: '48dp' 8 | 9 | ProgressBar: 10 | id: pb 11 | size_hint_x: .5 12 | size_hint_y: None 13 | height: '48dp' 14 | value: (app.time * 20) % 100. 15 | 16 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/rstdocument.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'RstDocument' 3 | fullscreen: True 4 | on_parent: if not args[1]: textinput.focus = False 5 | 6 | GridLayout: 7 | cols: 2 if root.width > root.height else 1 8 | spacing: '8dp' 9 | 10 | TextInput: 11 | id: textinput 12 | text: 13 | ('.. _top:\n' 14 | '\n' 15 | 'Hello world\n' 16 | '===========\n' 17 | '\n' 18 | 'This is an **emphased text**, *italic text*, ``interpreted text``.\n' 19 | 'And this is a reference to top_::\n' 20 | '\n' 21 | ' $ print("Hello world")\n') 22 | 23 | RstDocument: 24 | text: textinput.text 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/scatter.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Scatter' 3 | 4 | Widget: 5 | 6 | Scatter: 7 | id: scatter 8 | size_hint: None, None 9 | size: image.size 10 | 11 | Image: 12 | id: image 13 | source: 'data/faust_github.jpg' 14 | size: self.texture_size 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/screenmanager.kv: -------------------------------------------------------------------------------- 1 | #:import Factory kivy.factory.Factory 2 | 3 | ShowcaseScreen: 4 | name: 'ScreenManager' 5 | fullscreen: True 6 | 7 | BoxLayout: 8 | size_hint_y: None 9 | height: '48dp' 10 | 11 | Spinner: 12 | text: 'Default transition' 13 | values: ('SlideTransition', 'SwapTransition', 'FadeTransition', 'WipeTransition') 14 | on_text: sm.transition = Factory.get(self.text)() 15 | 16 | ScreenManager: 17 | id: sm 18 | 19 | Screen: 20 | name: 'screen1' 21 | canvas.before: 22 | Color: 23 | rgb: .8, .2, .2 24 | Rectangle: 25 | size: self.size 26 | 27 | AnchorLayout: 28 | Button: 29 | size_hint: None, None 30 | size: '150dp', '48dp' 31 | text: 'Go to screen 2' 32 | on_release: sm.current = 'screen2' 33 | 34 | Screen: 35 | name: 'screen2' 36 | canvas.before: 37 | Color: 38 | rgb: .2, .8, .2 39 | Rectangle: 40 | size: self.size 41 | AnchorLayout: 42 | Button: 43 | size_hint: None, None 44 | size: '150dp', '48dp' 45 | text: 'Go to screen 1' 46 | on_release: sm.current = 'screen1' 47 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/sliders.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Sliders' 3 | 4 | BoxLayout: 5 | size_hint_y: None 6 | height: '48dp' 7 | 8 | Label: 9 | text: 'Default' 10 | 11 | Slider: 12 | id: s1 13 | 14 | Label: 15 | text: '{}'.format(s1.value) 16 | 17 | 18 | BoxLayout: 19 | size_hint_y: None 20 | height: '48dp' 21 | 22 | Label: 23 | text: 'Stepped' 24 | 25 | Slider: 26 | id: s2 27 | step: 20 28 | 29 | Label: 30 | text: '{}'.format(s2.value) 31 | 32 | AnchorLayout: 33 | size_hint_y: None 34 | height: '100dp' 35 | 36 | GridLayout: 37 | cols: 2 38 | spacing: '8dp' 39 | size_hint_x: None 40 | width: self.minimum_width 41 | 42 | Slider: 43 | size_hint_x: None 44 | width: '48dp' 45 | orientation: 'vertical' 46 | value: s1.value 47 | on_value: s1.value = self.value 48 | 49 | Slider: 50 | size_hint_x: None 51 | width: '48dp' 52 | orientation: 'vertical' 53 | step: 20 54 | value: s2.value 55 | on_value: s2.value = self.value 56 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/spinner.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Spinner' 3 | fullscreen: True 4 | 5 | Spinner: 6 | text: 'Home' 7 | values: ('Home', 'Work', 'Other', 'Not defined') 8 | size_hint_y: None 9 | height: '48dp' 10 | 11 | Widget 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/splitter.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Splitter' 3 | fullscreen: True 4 | 5 | RelativeLayout: 6 | id: rl 7 | 8 | Splitter: 9 | sizable_from: 'right' 10 | min_size: 10 11 | max_size: rl.width 12 | Button: 13 | text: 'Panel' 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/switches.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'Switches' 3 | 4 | BoxLayout: 5 | size_hint_y: None 6 | height: '48dp' 7 | 8 | Label: 9 | text: 'Switch normal' 10 | Switch: 11 | 12 | BoxLayout: 13 | size_hint_y: None 14 | height: '48dp' 15 | 16 | Label: 17 | text: 'Switch active' 18 | Switch: 19 | active: True 20 | 21 | BoxLayout: 22 | size_hint_y: None 23 | height: '48dp' 24 | 25 | Label: 26 | text: 'Switch off & disabled' 27 | Switch: 28 | disabled: True 29 | active: False 30 | 31 | BoxLayout: 32 | size_hint_y: None 33 | height: '48dp' 34 | 35 | Label: 36 | text: 'Switch on & disabled' 37 | Switch: 38 | disabled: True 39 | active: True 40 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/tabbedpanel + layouts.kv: -------------------------------------------------------------------------------- 1 | #:import random random.random 2 | 3 | ShowcaseScreen: 4 | name: 'TabbedPanel + Layouts' 5 | fullscreen: True 6 | on_parent: if args[1] and tp.current_tab == tab_fl: app.showcase_floatlayout(fl) 7 | 8 | TabbedPanel: 9 | id: tp 10 | do_default_tab: False 11 | 12 | TabbedPanelItem: 13 | id: tab_fl 14 | text: 'FloatLayout' 15 | on_release: app.showcase_floatlayout(fl) 16 | FloatLayout: 17 | CFloatLayout: 18 | id: fl 19 | TabbedPanelItem: 20 | text: 'BoxLayout' 21 | on_release: app.showcase_boxlayout(box) 22 | FloatLayout 23 | CBoxLayout: 24 | id: box 25 | TabbedPanelItem: 26 | text: 'GridLayout' 27 | on_release: app.showcase_gridlayout(grid) 28 | FloatLayout 29 | CGridLayout: 30 | id: grid 31 | rows: 3 32 | TabbedPanelItem: 33 | on_release: app.showcase_stacklayout(stack) 34 | text: 'StackLayout' 35 | FloatLayout 36 | CStackLayout: 37 | id: stack 38 | TabbedPanelItem: 39 | text: 'AnchorLayout' 40 | on_release: app.showcase_anchorlayout(anchor) 41 | FloatLayout 42 | CAnchorLayout: 43 | id: anchor 44 | BoxLayout: 45 | orientation: 'vertical' 46 | size_hint: .4, .5 47 | Button 48 | Button 49 | text: 'anchor_x: {}'.format(anchor.anchor_x) 50 | Button 51 | text: 'anchor_y: {}'.format(anchor.anchor_y) 52 | Button 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | pos_hint: {'center_x': .5, 'center_y': .5} 63 | size_hint: .9, .9 64 | canvas.before: 65 | Color: 66 | rgba: .2, .3, .4, 1 67 | Rectangle: 68 | size: self.size 69 | pos: self.pos 70 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/textinputs.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'TextInputs' 3 | focused: ti_default 4 | on_parent: 5 | if not args[1] and self.focused: self.focused.focus = False 6 | if args[1]: ti_default.focus = True 7 | 8 | CTextInput 9 | size_hint_y: None 10 | height: '32dp' 11 | multiline: False 12 | text: 'Monoline textinput' 13 | 14 | CTextInput: 15 | id: ti_default 16 | size_hint_y: None 17 | height: '32dp' 18 | text: 'Focused textinput' 19 | focus: True 20 | 21 | CTextInput: 22 | size_hint_y: None 23 | height: '32dp' 24 | text: 'Password' 25 | password: True 26 | 27 | CTextInput: 28 | size_hint_y: None 29 | height: '32dp' 30 | text: 'Readonly textinput' 31 | readonly: True 32 | 33 | CTextInput: 34 | size_hint_y: None 35 | height: '48dp' 36 | text: 'Multiline textinput\nSecond line' 37 | multiline: True 38 | 39 | CTextInput: 40 | size_hint_y: None 41 | height: '32dp' 42 | disabled: True 43 | text: 'Disabled textinput' 44 | 45 | 46 | on_focus: 47 | screen = self.parent.parent.parent.parent 48 | if screen.parent: screen.focused = self 49 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/data/screens/togglebutton.kv: -------------------------------------------------------------------------------- 1 | ShowcaseScreen: 2 | name: 'ToggleButton' 3 | 4 | GridLayout: 5 | 6 | cols: 3 7 | spacing: '8dp' 8 | size_hint_y: None 9 | height: self.minimum_height 10 | 11 | Label: 12 | text: 'Choice 1' 13 | 14 | ToggleButton: 15 | size_hint_y: None 16 | height: '48dp' 17 | text: 'A' 18 | group: 'g1' 19 | 20 | ToggleButton: 21 | size_hint_y: None 22 | height: '48dp' 23 | text: 'B' 24 | group: 'g1' 25 | 26 | Label: 27 | text: 'Choice 2' 28 | 29 | ToggleButton: 30 | size_hint_y: None 31 | height: '48dp' 32 | text: 'A' 33 | group: 'g2' 34 | 35 | ToggleButton: 36 | size_hint_y: None 37 | height: '48dp' 38 | text: 'B' 39 | group: 'g2' 40 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Showcase of Kivy Features 3 | ========================= 4 | 5 | This showcases many features of Kivy. You should see a 6 | menu bar across the top with a demonstration area below. The 7 | first demonstration is the accordion layout. You can see, but not 8 | edit, the kv language code for any screen by pressing the bug or 9 | 'show source' icon. Scroll through the demonstrations using the 10 | left and right icons in the top right or selecting from the menu 11 | bar. 12 | 13 | The file showcase.kv describes the main container, while each demonstration 14 | pane is described in a separate .kv file in the data/screens directory. 15 | The image data/background.png provides the gradient background while the 16 | icons in data/icon directory are used in the control bar. The file 17 | data/faust_github.jpg is used in the Scatter pane. The icons are 18 | from `http://www.gentleface.com/free_icon_set.html` and licensed as 19 | Creative Commons - Attribution and Non-commercial Use Only; they 20 | sell a commercial license. 21 | 22 | The file android.txt is used to package the application for use with the 23 | Kivy Launcher Android application. For Android devices, you can 24 | copy/paste this directory into /sdcard/kivy/showcase on your Android device. 25 | 26 | ''' 27 | 28 | from time import time 29 | from kivy.app import App 30 | from os.path import dirname, join 31 | from kivy.lang import Builder 32 | from kivy.properties import NumericProperty, StringProperty, BooleanProperty,\ 33 | ListProperty 34 | from kivy.clock import Clock 35 | from kivy.animation import Animation 36 | from kivy.uix.screenmanager import Screen 37 | 38 | 39 | class ShowcaseScreen(Screen): 40 | fullscreen = BooleanProperty(False) 41 | 42 | def add_widget(self, *args, **kwargs): 43 | if 'content' in self.ids: 44 | return self.ids.content.add_widget(*args, **kwargs) 45 | return super(ShowcaseScreen, self).add_widget(*args, **kwargs) 46 | 47 | 48 | class ShowcaseApp(App): 49 | 50 | index = NumericProperty(-1) 51 | current_title = StringProperty() 52 | time = NumericProperty(0) 53 | show_sourcecode = BooleanProperty(False) 54 | sourcecode = StringProperty() 55 | screen_names = ListProperty([]) 56 | hierarchy = ListProperty([]) 57 | 58 | def build(self): 59 | self.title = 'hello world' 60 | Clock.schedule_interval(self._update_clock, 1 / 60.) 61 | self.screens = {} 62 | self.available_screens = sorted([ 63 | 'Buttons', 'ToggleButton', 'Sliders', 'ProgressBar', 'Switches', 64 | 'CheckBoxes', 'TextInputs', 'Accordions', 'FileChoosers', 65 | 'Carousel', 'Bubbles', 'CodeInput', 'DropDown', 'Spinner', 66 | 'Scatter', 'Splitter', 'TabbedPanel + Layouts', 'RstDocument', 67 | 'Popups', 'ScreenManager']) 68 | self.screen_names = self.available_screens 69 | curdir = dirname(__file__) 70 | self.available_screens = [join(curdir, 'data', 'screens', 71 | '{}.kv'.format(fn).lower()) for fn in self.available_screens] 72 | self.go_next_screen() 73 | 74 | def on_pause(self): 75 | return True 76 | 77 | def on_resume(self): 78 | pass 79 | 80 | def on_current_title(self, instance, value): 81 | self.root.ids.spnr.text = value 82 | 83 | def go_previous_screen(self): 84 | self.index = (self.index - 1) % len(self.available_screens) 85 | screen = self.load_screen(self.index) 86 | sm = self.root.ids.sm 87 | sm.switch_to(screen, direction='right') 88 | self.current_title = screen.name 89 | self.update_sourcecode() 90 | 91 | def go_next_screen(self): 92 | self.index = (self.index + 1) % len(self.available_screens) 93 | screen = self.load_screen(self.index) 94 | sm = self.root.ids.sm 95 | sm.switch_to(screen, direction='left') 96 | self.current_title = screen.name 97 | self.update_sourcecode() 98 | 99 | def go_screen(self, idx): 100 | self.index = idx 101 | self.root.ids.sm.switch_to(self.load_screen(idx), direction='left') 102 | self.update_sourcecode() 103 | 104 | def go_hierarchy_previous(self): 105 | ahr = self.hierarchy 106 | if len(ahr) == 1: 107 | return 108 | if ahr: 109 | ahr.pop() 110 | if ahr: 111 | idx = ahr.pop() 112 | self.go_screen(idx) 113 | 114 | def load_screen(self, index): 115 | if index in self.screens: 116 | return self.screens[index] 117 | screen = Builder.load_file(self.available_screens[index]) 118 | self.screens[index] = screen 119 | return screen 120 | 121 | def read_sourcecode(self): 122 | fn = self.available_screens[self.index] 123 | with open(fn) as fd: 124 | return fd.read() 125 | 126 | def toggle_source_code(self): 127 | self.show_sourcecode = not self.show_sourcecode 128 | if self.show_sourcecode: 129 | height = self.root.height * .3 130 | else: 131 | height = 0 132 | 133 | Animation(height=height, d=.3, t='out_quart').start( 134 | self.root.ids.sv) 135 | 136 | self.update_sourcecode() 137 | 138 | def update_sourcecode(self): 139 | if not self.show_sourcecode: 140 | self.root.ids.sourcecode.focus = False 141 | return 142 | self.root.ids.sourcecode.text = self.read_sourcecode() 143 | self.root.ids.sv.scroll_y = 1 144 | 145 | def showcase_floatlayout(self, layout): 146 | 147 | def add_button(*t): 148 | if not layout.get_parent_window(): 149 | return 150 | if len(layout.children) > 5: 151 | layout.clear_widgets() 152 | layout.add_widget(Builder.load_string(''' 153 | #:import random random.random 154 | Button: 155 | size_hint: random(), random() 156 | pos_hint: {'x': random(), 'y': random()} 157 | text: 158 | 'size_hint x: {} y: {}\\n pos_hint x: {} y: {}'.format(\ 159 | self.size_hint_x, self.size_hint_y, self.pos_hint['x'],\ 160 | self.pos_hint['y']) 161 | ''')) 162 | Clock.schedule_once(add_button, 1) 163 | Clock.schedule_once(add_button) 164 | 165 | def showcase_boxlayout(self, layout): 166 | 167 | def add_button(*t): 168 | if not layout.get_parent_window(): 169 | return 170 | if len(layout.children) > 5: 171 | layout.orientation = 'vertical'\ 172 | if layout.orientation == 'horizontal' else 'horizontal' 173 | layout.clear_widgets() 174 | layout.add_widget(Builder.load_string(''' 175 | Button: 176 | text: self.parent.orientation if self.parent else '' 177 | ''')) 178 | Clock.schedule_once(add_button, 1) 179 | Clock.schedule_once(add_button) 180 | 181 | def showcase_gridlayout(self, layout): 182 | 183 | def add_button(*t): 184 | if not layout.get_parent_window(): 185 | return 186 | if len(layout.children) > 15: 187 | layout.rows = 3 if layout.rows is None else None 188 | layout.cols = None if layout.rows == 3 else 3 189 | layout.clear_widgets() 190 | layout.add_widget(Builder.load_string(''' 191 | Button: 192 | text: 193 | 'rows: {}\\ncols: {}'.format(self.parent.rows, self.parent.cols)\ 194 | if self.parent else '' 195 | ''')) 196 | Clock.schedule_once(add_button, 1) 197 | Clock.schedule_once(add_button) 198 | 199 | def showcase_stacklayout(self, layout): 200 | orientations = ('lr-tb', 'tb-lr', 201 | 'rl-tb', 'tb-rl', 202 | 'lr-bt', 'bt-lr', 203 | 'rl-bt', 'bt-rl') 204 | 205 | def add_button(*t): 206 | if not layout.get_parent_window(): 207 | return 208 | if len(layout.children) > 11: 209 | layout.clear_widgets() 210 | cur_orientation = orientations.index(layout.orientation) 211 | layout.orientation = orientations[cur_orientation - 1] 212 | layout.add_widget(Builder.load_string(''' 213 | Button: 214 | text: self.parent.orientation if self.parent else '' 215 | size_hint: .2, .2 216 | ''')) 217 | Clock.schedule_once(add_button, 1) 218 | Clock.schedule_once(add_button) 219 | 220 | def showcase_anchorlayout(self, layout): 221 | 222 | def change_anchor(self, *l): 223 | if not layout.get_parent_window(): 224 | return 225 | anchor_x = ('left', 'center', 'right') 226 | anchor_y = ('top', 'center', 'bottom') 227 | if layout.anchor_x == 'left': 228 | layout.anchor_y = anchor_y[anchor_y.index(layout.anchor_y) - 1] 229 | layout.anchor_x = anchor_x[anchor_x.index(layout.anchor_x) - 1] 230 | 231 | Clock.schedule_once(change_anchor, 1) 232 | Clock.schedule_once(change_anchor, 1) 233 | 234 | def _update_clock(self, dt): 235 | self.time = time() 236 | 237 | 238 | if __name__ == '__main__': 239 | ShowcaseApp().run() 240 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/requirements.txt: -------------------------------------------------------------------------------- 1 | kivy -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/sample.gif -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/kivy_/showcase.kv: -------------------------------------------------------------------------------- 1 | #:kivy 1.8.0 2 | #:import KivyLexer kivy.extras.highlight.KivyLexer 3 | #:import Factory kivy.factory.Factory 4 | 5 | 6 | background_color: .4, .4, .4, 1 7 | 8 | 9 | canvas.before: 10 | Color: 11 | rgba: 0.128, 0.128, 0.128, 1 12 | Rectangle: 13 | size: self.size 14 | pos: self.pos 15 | border: 27, 20, 12, 12 16 | background_normal: 'atlas://data/images/defaulttheme/action_group' 17 | option_cls: Factory.ActionSpinnerOptions 18 | 19 | : 20 | on_size: self.width = '220dp' 21 | 22 | : 23 | ScrollView: 24 | do_scroll_x: False 25 | do_scroll_y: False if root.fullscreen else (content.height > root.height - dp(16)) 26 | AnchorLayout: 27 | size_hint_y: None 28 | height: root.height if root.fullscreen else max(root.height, content.height) 29 | GridLayout: 30 | id: content 31 | cols: 1 32 | spacing: '8dp' 33 | padding: '8dp' 34 | size_hint: (1, 1) if root.fullscreen else (.8, None) 35 | height: self.height if root.fullscreen else self.minimum_height 36 | 37 | 38 | BoxLayout: 39 | orientation: 'vertical' 40 | 41 | canvas.before: 42 | Color: 43 | rgb: .6, .6, .6 44 | Rectangle: 45 | size: self.size 46 | source: 'data/background.png' 47 | 48 | ActionBar: 49 | 50 | ActionView: 51 | id: av 52 | ActionPrevious: 53 | with_previous: (False if sm.current_screen.name == 'button' else True) if sm.current_screen else False 54 | title: 'Showcase' + ('' if not app.current_title else ' - {}'.format(app.current_title)) 55 | on_release: app.go_hierarchy_previous() 56 | 57 | ActionSpinner: 58 | id: spnr 59 | important: True 60 | text: 'Jump to Screen' 61 | values: app.screen_names 62 | on_text: 63 | if sm.current != args[1]:\ 64 | idx = app.screen_names.index(args[1]);\ 65 | app.go_screen(idx) 66 | ActionToggleButton: 67 | text: 'Toggle sourcecode' 68 | icon: 'data/icons/bug.png' 69 | on_release: app.toggle_source_code() 70 | ActionButton: 71 | text: 'Previous screen' 72 | icon: 'data/icons/chevron-left.png' 73 | on_release: app.go_previous_screen() 74 | 75 | ActionButton: 76 | text: 'Next screen' 77 | icon: 'data/icons/chevron-right.png' 78 | on_release: app.go_next_screen() 79 | important: True 80 | 81 | ScrollView: 82 | id: sv 83 | size_hint_y: None 84 | height: 0 85 | 86 | CodeInput: 87 | id: sourcecode 88 | lexer: KivyLexer() 89 | text: app.sourcecode 90 | readonly: True 91 | size_hint_y: None 92 | font_size: '12sp' 93 | height: self.minimum_height 94 | 95 | ScreenManager: 96 | id: sm 97 | on_current_screen: 98 | spnr.text = args[1].name 99 | idx = app.screen_names.index(args[1].name) 100 | if idx > -1: app.hierarchy.append(idx) 101 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/README.md: -------------------------------------------------------------------------------- 1 | How to Use: 2 | =========== 3 | 4 | * Create and activate a virtual environment: 5 | 6 | virtualenv venv 7 | 8 | venv\Scripts\Activate 9 | 10 | * Install dependencies: 11 | 12 | pip install -r requirements.txt 13 | 14 | * Run the GUI demo: 15 | 16 | python simple_gui_builder.py 17 | 18 | 19 | Note: 20 | ---- 21 | This Demo is taken from this repository and updated for python 3: 22 | https://github.com/PriestTheBeast/SimpleGUIBuilder 23 | 24 | 25 | Sample: 26 | ---- 27 | SimpleGUIBuilder: 28 | 29 | ![SimpleGUIBuilder](SimpleGUIBuilder.gif) 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/SimpleGUIBuilder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/pysimplegui_/SimpleGUIBuilder.gif -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/layout_parser.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import sys 3 | import ast 4 | 5 | import PySimpleGUI as sg 6 | 7 | CONTAINER_ELEMENTS = (sg.Column, sg.Frame, sg.Tab) 8 | CONTAINER_LAYOUT_ARG = {sg.Column: 0, sg.Frame: 1, sg.Tab: 1} 9 | 10 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass)) 11 | 12 | 13 | # This file is one I don't wish to revisit so soon, but it does all the hard work of 14 | # taking a layout in string format and interpret it, making possible creating a Tree structure from it 15 | # all I have to say is, god damn handling strings in parsing the string layout xD 16 | 17 | # Parses info into a list of something, start and end are the delimiters, 18 | # save_all I think makes it also save the beginning b4 the start delimiter 19 | # filter_beginning allows it to ignore some chars at the start of the info, like " " and "," 20 | def parse_with_delimiters(info, start, end, save_all=False, filter_beginning=None): 21 | rows = [] 22 | row = [] 23 | quote = None 24 | escaped = False 25 | deep_lvl = 0 26 | in_beginning = True 27 | for letter in info: 28 | if in_beginning: 29 | if filter_beginning is None or letter not in filter_beginning: 30 | in_beginning = False 31 | else: 32 | continue 33 | 34 | if escaped: 35 | escaped = False 36 | row.append(letter) 37 | continue 38 | if letter in ("'", '"'): 39 | if quote is None: 40 | quote = letter 41 | elif letter == quote: 42 | quote = None 43 | row.append(letter) 44 | continue 45 | if quote is not None: 46 | if letter == "\\": 47 | escaped = True 48 | row.append(letter) 49 | continue 50 | 51 | if letter == start: 52 | if save_all or deep_lvl > 0: 53 | row.append(letter) 54 | deep_lvl += 1 55 | elif letter == end: 56 | if deep_lvl == 1: 57 | if save_all: 58 | row.append(letter) 59 | rows.append("".join(row)) 60 | row = [] 61 | in_beginning = True 62 | else: 63 | row.append(letter) 64 | deep_lvl -= 1 65 | elif save_all or deep_lvl > 0: 66 | row.append(letter) 67 | 68 | return rows 69 | 70 | 71 | def parse_with_single_char(info, ch, filter_beginning=None): 72 | if not info: 73 | return [] 74 | rows = [] 75 | row = [] 76 | quote = None 77 | escaped = False 78 | collection_start = {"(": ")", "{": "}", "[": "]"} 79 | collection_char = None 80 | deep_lvl = 0 81 | in_beginning = True 82 | for letter in info: 83 | if in_beginning: 84 | if filter_beginning is None or letter not in filter_beginning: 85 | in_beginning = False 86 | else: 87 | continue 88 | 89 | if escaped: 90 | escaped = False 91 | row.append(letter) 92 | continue 93 | if letter in ("'", '"'): 94 | if quote is None: 95 | quote = letter 96 | elif letter == quote: 97 | quote = None 98 | row.append(letter) 99 | continue 100 | if quote is not None: 101 | if letter == "\\": 102 | escaped = True 103 | row.append(letter) 104 | continue 105 | 106 | if collection_char is not None: 107 | if letter == collection_start[collection_char]: 108 | deep_lvl -= 1 109 | if deep_lvl == 0: 110 | collection_char = None 111 | elif letter == collection_char: 112 | deep_lvl += 1 113 | else: 114 | if letter in collection_start: 115 | collection_char = letter 116 | deep_lvl += 1 117 | elif letter == ch: 118 | rows.append("".join(row)) 119 | row = [] 120 | in_beginning = True 121 | continue 122 | row.append(letter) 123 | 124 | rows.append("".join(row)) 125 | return rows 126 | 127 | 128 | def strip_except_inside_quote(info): 129 | new_info = "" 130 | 131 | quote = None 132 | escaped = False 133 | for letter in info: 134 | if escaped: 135 | escaped = False 136 | new_info += letter 137 | continue 138 | if letter in ("'", '"'): 139 | if quote is None: 140 | quote = letter 141 | elif letter == quote: 142 | quote = None 143 | new_info += letter 144 | continue 145 | if quote is not None: 146 | if letter == "\\": 147 | escaped = True 148 | new_info += letter 149 | continue 150 | 151 | if letter not in (" ", "\n", "\t"): 152 | new_info += letter 153 | 154 | return new_info 155 | 156 | 157 | def parse_string_layout(string_layout): 158 | # remove first "[ ]" 159 | rows_string = strip_except_inside_quote(string_layout)[1:-1] 160 | 161 | # separate into rows 162 | rows = parse_with_delimiters(rows_string, "[", "]") 163 | 164 | # separate each row into list of elements 165 | tree = [] 166 | for row in rows: 167 | tree.append(parse_with_delimiters(row, "(", ")", save_all=True, filter_beginning=(" ", ","))) 168 | 169 | # for row in tree: 170 | # for element in row: 171 | # print(element, end=" - ") 172 | # print() 173 | 174 | # separate each element into the element name and list of evaluated args 175 | new_tree = [] 176 | for row in tree: 177 | new_row = [] 178 | new_tree.append(new_row) 179 | for element in row: 180 | element_name_ended = False 181 | element_name = "" 182 | args = "" 183 | for letter in element: 184 | if element_name_ended: 185 | args += letter 186 | elif letter == "(": 187 | element_name_ended = True 188 | args += letter 189 | else: 190 | element_name += letter 191 | 192 | args_list = parse_with_single_char(args[1:-1], ",") 193 | are_kargs = False 194 | args_values = [] 195 | kargs_values = {} 196 | for i, arg in enumerate(args_list): 197 | container_with_args = (GUI_CLASSES[element_name[3:]] in CONTAINER_ELEMENTS 198 | and i == CONTAINER_LAYOUT_ARG[GUI_CLASSES[element_name[3:]]]) 199 | if not are_kargs and "=" in arg and not container_with_args: 200 | eq_pos = arg.index("=") 201 | if ("'" not in arg or eq_pos < arg.index("'")) and ('"' not in arg or eq_pos < arg.index('"')): 202 | are_kargs = True 203 | 204 | if not are_kargs: 205 | if container_with_args: 206 | args_values.append(parse_string_layout(arg)) 207 | else: 208 | try: 209 | args_values.append(ast.literal_eval(arg)) 210 | except Exception as e: 211 | print(arg) 212 | raise Exception(str(e), arg) from e 213 | else: 214 | kargs_values[arg[:arg.index("=")]] = ast.literal_eval(arg[arg.index("=") + 1:]) 215 | 216 | new_row.append((element_name[3:], args_values, kargs_values)) 217 | 218 | # for row in new_tree: 219 | # for element in row: 220 | # print(element, end=" - ") 221 | # print() 222 | 223 | return new_tree 224 | 225 | 226 | # Test a very bad case scenario 227 | def main(): 228 | test_layout = '[ \n \t ' \ 229 | '[ sg.Text ( " This is a very \\" sure then \\" \' right \' ][ basic PySimpleGUI layout")],' \ 230 | '[sg.Input()],' \ 231 | '[ sg.Button( \' Button "right" \\\' maybe \\\' \'), sg.Button( "Exit", 4, 6, key= "-nope-", visible=True)]' \ 232 | ']' 233 | 234 | print(parse_string_layout(test_layout)) 235 | 236 | 237 | if __name__ == "__main__": 238 | main() 239 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/requirements.txt: -------------------------------------------------------------------------------- 1 | PySimpleGUI -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/simple_gui_builder.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import sys 3 | import time 4 | 5 | import PySimpleGUI as sg 6 | from tree_node import TreeNode 7 | 8 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass)) 9 | 10 | ELEMB_KEY = "ELEMB-" 11 | 12 | 13 | # Function that returns the layout of the middle Elements Frame, where you choose the an element 14 | # It's lists made by hand, but any Element that is not in any list will be added at the last list (Others) 15 | def make_elements_frame(): 16 | layout = [] 17 | 18 | elements = set(GUI_CLASSES.values()) 19 | elements_names = [e.__name__ for e in elements if 20 | issubclass(e, sg.Element) and e not in (sg.Element, sg.ErrorElement)] 21 | 22 | # Makes a button for each string in the given list of strings 23 | make_button_row = (lambda element_list: [sg.Button(button_text=e, key=ELEMB_KEY + e) for e in element_list]) 24 | 25 | layout.append([sg.Button(button_text="Delete Element", button_color=("black", "darkred"), key="DeleteElement"), 26 | sg.Button('Move Element Up', key="MoveUp"), sg.Button('Move Element Down', key="MoveDown")]) 27 | 28 | layout.append([sg.Text(text="Main Elements")]) 29 | main_elements = ["Text", "InputText", "Output", "Button", "Combo", "Listbox", "Radio", "Checkbox"] 30 | layout.append(make_button_row(main_elements)) 31 | 32 | layout.append([sg.Text(text="Containers")]) 33 | container_elems = ["TabGroup", "Tab", "Frame", "Column"] 34 | layout.append(make_button_row(container_elems)) 35 | 36 | layout.append([sg.Text(text="Separators")]) 37 | design_elems = ["HorizontalSeparator", "VerticalSeparator"] 38 | layout.append(make_button_row(design_elems)) 39 | 40 | layout.append([sg.Text(text="Menus")]) 41 | organization_elems = ["Menu", "MenuButton", "OptionMenu", "ButtonMenu"] 42 | layout.append(make_button_row(organization_elems)) 43 | 44 | layout.append([sg.Text(text="Structured Info")]) 45 | structured_info_elems = ["Tree", "Table"] 46 | layout.append(make_button_row(structured_info_elems)) 47 | 48 | layout.append([sg.Text(text="Images/Drawing")]) 49 | drawing_elems = ["Image", "Graph", "Canvas"] 50 | layout.append(make_button_row(drawing_elems)) 51 | 52 | layout.append([sg.Text(text="Others")]) 53 | other_elements = ["Slider", "Spin", "Multiline", "ProgressBar", "StatusBar"] 54 | for e in elements_names: 55 | if e not in (main_elements + container_elems + design_elems + organization_elems + 56 | structured_info_elems + drawing_elems + other_elements + ["Pane"]): 57 | other_elements.append(e) 58 | # print(e) 59 | layout.append(make_button_row(other_elements)) 60 | 61 | return layout 62 | 63 | 64 | # Creates the main window layout from scratch. Necessary to reset the window. 65 | def make_main_window(tree): 66 | 67 | # STEP 1 define the layout 68 | tree_elem = sg.Tree(tree.get_tree_data(), ["Visible", "Key"], 69 | key="-TREE-", enable_events=True, show_expanded=True, col0_width=30, 70 | auto_size_columns=False, col_widths=[5, 10]) 71 | 72 | frame_elem = sg.Frame("Elements", make_elements_frame()) 73 | 74 | frame_prop = sg.Frame("Properties", []) 75 | 76 | stuff_row = [tree_elem, frame_elem, frame_prop] 77 | 78 | layout = [ 79 | [sg.Text('M.M. - Welcome to a layout builder for PySimpleGUI, made with PySimpleGUI!'), 80 | sg.Button("About"), sg.Button('Preview'), sg.Button('Import'), sg.Button('Export'), 81 | sg.Button("Save"), sg.Button("Load"), sg.Button("Setup"), sg.Button('Apply Properties'), 82 | sg.Text(size=(20, 1), auto_size_text=True, key="Status_text", text_color="Light Green")], 83 | stuff_row 84 | ] 85 | 86 | # STEP 2 - create the window 87 | # print(location, size) 88 | w = sg.Window('My new window', layout, finalize=True, resizable=True) 89 | w.maximize() 90 | 91 | # Resizes the Main 3 elements according to the window size 92 | size = tuple(w.Size) 93 | padded_size = (size[0], size[1] - 50) 94 | elem_w, elem_h = padded_size[0] // len(stuff_row), padded_size[1] 95 | for elem in stuff_row: 96 | elem.set_size((elem_w, elem_h)) 97 | 98 | return w, tree_elem, frame_elem, frame_prop, (elem_w, elem_h) 99 | 100 | 101 | # Main Function :) 102 | def main(): 103 | print("Hello World!") 104 | sg.theme('DarkAmber') # No gray windows please! 105 | 106 | # auto_load from file if it exists 107 | try: 108 | f = open("autosave.txt") 109 | template_string_layout = f.read() 110 | f.close() 111 | except: 112 | print("There is no autosave file. Giving template layout.") 113 | template_string_layout = "[" \ 114 | "[sg.Text(text='This is a very basic PySimpleGUI layout')], " \ 115 | "[sg.InputText()], " \ 116 | "[sg.Button('Button', key='-ExampleKey-'), sg.Button(button_text='Exit')] " \ 117 | "]" 118 | 119 | # Create tree structure from the string layout 120 | # This tree structure is different from the TreeElement, 121 | # the TreeElement shows a simplified visual representation of this Tree 122 | tree = TreeNode.parse_string_layout(template_string_layout) 123 | 124 | # Create window 125 | window, tree_element, frame_elements, frame_properties, elem_size = make_main_window(tree) 126 | 127 | # Initial variable setup 128 | current_tree_node, current_property = None, None 129 | property_count = 0 130 | win2 = None 131 | win2_active = False 132 | current_time = time.time() 133 | 134 | # The event loop 135 | while True: 136 | # Read the event that happened and the values dictionary 137 | event, values = window.read() 138 | 139 | # print(event, values) 140 | window["Status_text"].update(value="") 141 | 142 | # If user closed window with X or if user clicked "Exit" button then exit 143 | if event in (sg.WIN_CLOSED, 'Exit'): 144 | break 145 | 146 | # I don't remember why this is here 147 | if event is None: 148 | continue 149 | 150 | # If user clicked button on an element in the tree 151 | if event == "-TREE-": 152 | tree_node = values[event][0] 153 | if tree_node != current_tree_node: 154 | if current_tree_node is not None: 155 | current_property.update(visible=False) 156 | 157 | if property_count > 50: 158 | window.close() 159 | window, tree_element, frame_elements, frame_properties, elem_size = make_main_window(tree) 160 | property_count = 0 161 | 162 | current_tree_node = tree_node 163 | current_property = tree_node.get_properties_layout(elem_size, property_count + 1) 164 | window.extend_layout(frame_properties, [[sg.pin(current_property)]]) 165 | property_count += 1 166 | 167 | # If user clicked button on an element in the tree 168 | if event == "Apply Properties": 169 | selected_tree_element = values["-TREE-"] 170 | if current_tree_node is None: 171 | sg.popup("No element selected") 172 | else: 173 | try: 174 | if selected_tree_element: 175 | current_tree_node = selected_tree_element[0] 176 | if current_tree_node.element in ("Root", "Row"): 177 | sg.popup("Root/Row have no properties to apply.") 178 | else: 179 | current_tree_node.apply_properties(values, property_count) 180 | # sg.popup_auto_close("Properties Applied", auto_close_duration=3) 181 | window["Status_text"].update(value='Properties Applied') 182 | tree_element.update(values=tree.get_tree_data()) 183 | except Exception as e: 184 | sg.popup_error("Error: " + str(e) + 185 | "\nError applying properties. Prob tried to enter a bad value to a property.") 186 | 187 | # If user clicked on an element to add 188 | if event[:6] == ELEMB_KEY: 189 | element_name = event[6:] 190 | selected_tree_element = values["-TREE-"] 191 | if not selected_tree_element: 192 | sg.popup("No element selected") 193 | else: 194 | selected_tree_element[0].add_tree_node(TreeNode(GUI_CLASSES[element_name])) 195 | tree_element.update(values=tree.get_tree_data()) 196 | 197 | # If user clicked on the DeleteElement button 198 | if event == "DeleteElement": 199 | selected_tree_element = values["-TREE-"] 200 | if not selected_tree_element: 201 | sg.popup("No element selected") 202 | else: 203 | response = sg.popup_ok_cancel("Confirm deletion?", selected_tree_element[0]) 204 | if response == "OK": 205 | selected_tree_element[0].remove_tree_node() 206 | tree_element.update(values=tree.get_tree_data()) 207 | 208 | # If user clicked on the MoveUp element button 209 | if event == "MoveUp": 210 | selected_tree_element = values["-TREE-"] 211 | if not selected_tree_element: 212 | sg.popup("No element selected") 213 | else: 214 | selected_tree_element[0].move_tree_node("back") 215 | tree_element.update(values=tree.get_tree_data()) 216 | 217 | # If user clicked on the MoveDown element button 218 | if event == "MoveDown": 219 | selected_tree_element = values["-TREE-"] 220 | if not selected_tree_element: 221 | sg.popup("No element selected") 222 | else: 223 | selected_tree_element[0].move_tree_node("forward") 224 | tree_element.update(values=tree.get_tree_data()) 225 | 226 | # If user clicked on the Import button 227 | # This allows the user to import a layout to SimpleGUIBuilder from a string layout 228 | if event == "Import": 229 | text = sg.popup_get_text("ATTENTION! Importing will replace current layout.\n" 230 | "Also, importing '[]' is a great way to clear if you want to.") 231 | if text == "": 232 | sg.popup_auto_close("Text is empty. Importing cancelled.", auto_close_duration=3) 233 | elif text is None: 234 | # sg.popup_auto_close("Import cancelled.", auto_close_duration=3) 235 | pass 236 | else: 237 | try: 238 | tree = TreeNode.parse_string_layout(text) 239 | tree_element.update(values=tree.get_tree_data()) 240 | except Exception as e: 241 | sg.popup_error(("Error: " + str(e) if len(e.args) == 1 242 | else "Error: " + str(e.args[0]) + "\n, in this place: " + str(e.args[1])) + 243 | "\n Importing cancelled or Error in importing (mistake in the text given).") 244 | 245 | # If user clicked on the Export button 246 | # Shows the correspondent string layout for the GUI being built 247 | if event == "Export": 248 | sg.popup_get_text("Current layout:", default_text=tree.layout_to_string()) 249 | 250 | # If user clicked on the Save button 251 | # Export button but string layout goes to file 252 | if event == "Save": 253 | try: 254 | file_path = sg.popup_get_file("Where do you want to save your file?", save_as=True) 255 | if file_path is not None: 256 | with open(file_path, "w") as f: 257 | f.write(tree.layout_to_string()) 258 | except Exception as e: 259 | sg.popup_error("Error: " + str(e) + 260 | "\nError in writing to file.") 261 | 262 | # If user clicked on the Load button 263 | # Import button but string layout comes from file 264 | if event == "Load": 265 | try: 266 | file_path = sg.popup_get_file("ATTENTION! Loading will replace current layout.\n" 267 | "Where is the file you want to load?") 268 | if file_path is not None: 269 | with open(file_path, "r") as f: 270 | text = f.read() 271 | if text == "": 272 | sg.popup_auto_close("Text is empty. Loading cancelled.", auto_close_duration=3) 273 | elif text is None: 274 | # sg.popup_auto_close("Import cancelled.", auto_close_duration=3) 275 | pass 276 | else: 277 | try: 278 | tree = TreeNode.parse_string_layout(text) 279 | tree_element.update(values=tree.get_tree_data()) 280 | except Exception as e: 281 | sg.popup_error(("Error: " + str(e) if len(e.args) == 1 282 | else "Error: " + str(e.args[0]) + "\n, in this place: " + str(e.args[1])) + 283 | "\n Loading cancelled or Error in Loading (mistake in the text given).") 284 | except Exception as e: 285 | sg.popup_error("Error: " + str(e) + 286 | "\nError in reading from file.") 287 | 288 | # If user clicked on the Setup button 289 | # This button will create a file from the template in here 290 | # https://pysimplegui.trinket.io/demo-programs#/demo-programs/the-basic-pysimplegui-program 291 | # Except with the layout switched to the exported string layout 292 | # Most of it is in "tree.write_to_file" function 293 | if event == "Setup": 294 | try: 295 | file_path = sg.popup_get_file("Where do you want to save your Setup file?", save_as=True) 296 | if file_path is not None: 297 | with open(file_path, "w") as f: 298 | tree.write_to_file(f) 299 | except Exception as e: 300 | sg.popup_error("Error: " + str(e) + 301 | "\nError in writing to file.") 302 | 303 | # If user clicked on the About button 304 | # This button will show info I want to show for people to see if anyone wants to see xD 305 | if event == "About": 306 | sg.popup("I don't really like frontend but I really like the idea of giving my backend/terminal programs " 307 | "something more pleasurable to interact with.\n\n" 308 | "That's when I came across PySimpleGUI, a simple solution to quickly give my programs an interactive front. " 309 | "But in checking out PySimpleGUI I found I wanted more and had an idea: \n" 310 | "It would be nice if PySimpleGUI and therefore GUI making/designing was in itself more interactive.\n\n" 311 | "And that's how SimpleGUIBuilder came to be:\n" 312 | "A GUI for creating/designing GUI's for PySimpleGUI, made with PySimpleGUI.\n" 313 | "I hope this will be useful to people :)\n\n" 314 | "Made by Miguel Martins.\n\n" 315 | "If you want to support me: https://www.buymeacoffee.com/MMartins\n\n" 316 | "Version - 1.0.2") 317 | 318 | # Handle window preview 319 | # Check if in the meantime if the window was closed 320 | # Have to see if I need timeout and win2_active variable with the window being modal 321 | if win2_active: 322 | ev, v = win2.read(timeout=100) 323 | if ev == sg.WIN_CLOSED: 324 | win2_active = False 325 | win2.close() 326 | 327 | if not win2_active and event == "Preview": 328 | try: 329 | win2 = sg.Window('Preview', tree.get_layout(), finalize=True, modal=True) 330 | win2_active = True 331 | except Exception as e: 332 | sg.popup_error("Error: " + str(e) + 333 | "\nError in preview (mistake in the layout). You can try finding it with export") 334 | 335 | # autosave 336 | if time.time() - current_time > 180: 337 | try: 338 | with open("autosave.txt", "w") as f: 339 | f.write(tree.layout_to_string()) 340 | current_time = time.time() 341 | except Exception as e: 342 | sg.popup_error("Error: " + str(e) + 343 | "\nError in autosaving to file") 344 | 345 | window.close() 346 | # autosave 347 | try: 348 | with open("autosave.txt", "w") as f: 349 | f.write(tree.layout_to_string()) 350 | except Exception as e: 351 | sg.popup_error("Error: " + str(e) + 352 | "\nError in autosaving to file") 353 | 354 | 355 | if __name__ == "__main__": 356 | try: 357 | main() 358 | except Exception as e: 359 | sg.popup_error("Error: " + str(e) + 360 | "\nGeneral error. This error exists so the program doesn't just crash." + 361 | "\nAn error happened that I didn't catch properly, so no good message to help in what's wrong" + 362 | " except the prob bluberish above :(" + 363 | "\nI hope no one sees this, but you know how it is, it will happen ¯\_(ツ)_/¯") 364 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/pysimplegui_/tree_node.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import sys 3 | import ast 4 | 5 | import PySimpleGUI as sg 6 | import layout_parser as ps 7 | 8 | CONTAINER_ELEMENTS = (sg.Column, sg.Frame, sg.Tab, sg.TabGroup) 9 | CONTAINER_LAYOUT_ARG = {sg.Column: 0, sg.Frame: 1, sg.Tab: 1, sg.TabGroup: 0} 10 | 11 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass)) 12 | 13 | 14 | # This custom structure is core of the whole thing 15 | # It's my centralized way of storing a layout, built it, modify it, modify its initial parameters 16 | # String layouts are translated into the Tree structure and vice-versa through the layout_parser 17 | # which could also be called string_layout_interpreter? anyway 18 | class TreeNode: 19 | 20 | def __init__(self, element, parent=None): 21 | # element class 22 | self.element = element 23 | self.parent = parent 24 | 25 | self.container = [] 26 | if element in CONTAINER_ELEMENTS: 27 | self.container.append(TreeNode("Root")) 28 | 29 | self.necessary_args_name = [] 30 | self.necessary_args = {} 31 | self.optional_args = {} 32 | # this exists so it can compare and not print all optional args when creating the string layout 33 | # optional args in export 34 | self.default_optional_args = {} 35 | # this exists so the user can give first optional args without key 36 | # Example: Button("OMG") instead of Button(button_text="less omg") 37 | self.default_optional_args_name = [] 38 | self.args_docs = {} 39 | 40 | if self.element not in ("Row", "Root"): 41 | self.set_properties() 42 | 43 | # If element is ("Row", "Root") give, else give the Class name 44 | def __str__(self): 45 | if isinstance(self.element, str): 46 | return self.element 47 | 48 | return self.element.__name__ 49 | 50 | # If the element is a container Element, i.e. can contain other elements 51 | def is_container(self): 52 | return self.element in ("Row", "Root") + CONTAINER_ELEMENTS 53 | 54 | # Adds a child node to this node, creates Row element if necessary 55 | def add_tree_node(self, tree_node): 56 | # how does this work with container elements - it has logic outside it 57 | # and if someone wants to add something and self is not a container then nothing happens 58 | if self.is_container(): 59 | if self.element == "Row" or tree_node.element == "Row": 60 | self.container.append(tree_node) 61 | tree_node.parent = self 62 | return self 63 | 64 | row = TreeNode("Row", parent=self).add_tree_node(tree_node) 65 | tree_node.parent = row 66 | self.container.append(row) 67 | else: 68 | sg.popup("Target element is not a Container/Root/Row") 69 | 70 | # Removes itself from parent node 71 | def remove_tree_node(self): 72 | if self.parent is None: 73 | sg.popup("Cannot remove Root element") 74 | else: 75 | self.parent.container.remove(self) 76 | 77 | # Moves tree node location in parent node 78 | def move_tree_node(self, direction): 79 | if self.parent is None: 80 | sg.popup("Cannot move Root element") 81 | else: 82 | container = self.parent.container 83 | position = container.index(self) 84 | if direction == "back": 85 | container[position], container[position - 1] = container[position - 1], container[position] 86 | else: 87 | next_pos = position + 1 if position + 1 != len(container) else 0 88 | # if next_pos == len(container): 89 | # next_pos = 0 90 | container[position], container[next_pos] = container[next_pos], container[position] 91 | 92 | # Returns the layout object of the Tree, which is given to the Preview window to render 93 | def get_layout(self): 94 | # if self.element in ("Root", "Row"): 95 | if self.container: 96 | if self.element in ("Root", "Row"): 97 | return [x.get_layout() for x in self.container] 98 | 99 | return self.element(*[self.necessary_args[x] if x != "layout" else self.container[0].get_layout() 100 | for x in self.necessary_args_name], **self.optional_args) 101 | 102 | # if element is empty root or row 103 | if self.element in ("Root", "Row"): 104 | if self.element == "Root": 105 | raise Exception("Root cannot be empty") 106 | return [] 107 | 108 | return self.element(*[self.necessary_args[x] for x in self.necessary_args_name], **self.optional_args) 109 | 110 | # Returns the tree data representation of the Tree, 111 | # which is given to the TreeElement for visualization and element picking 112 | def get_tree_data(self, treedata=None, parent=""): 113 | if treedata is None: 114 | treedata = sg.TreeData() 115 | 116 | treedata.Insert(parent, self, str(self), 117 | [self.optional_args["visible"], self.optional_args["key"]] 118 | if self.element not in ("Row", "Root") else ["_", "_"]) 119 | 120 | for x in self.container: 121 | x.get_tree_data(treedata, self) 122 | 123 | return treedata 124 | 125 | # Builds and returns a Tree structure from a previously parsed layout 126 | # It is separate from the parse_string_layout for recursion purposes 127 | @staticmethod 128 | def make_tree_from_parsed_layout(parsed_layout): 129 | tree = TreeNode("Root") 130 | 131 | for parsed_row in parsed_layout: 132 | row = TreeNode("Row") 133 | tree.add_tree_node(row) 134 | for parsed_elem in parsed_row: 135 | print(parsed_elem) 136 | elem = TreeNode(GUI_CLASSES[parsed_elem[0]]) 137 | row.add_tree_node(elem) 138 | for i in range(len(elem.necessary_args_name)): 139 | elem.necessary_args[elem.necessary_args_name[i]] = parsed_elem[1][i] 140 | necessary_to_optional = parsed_elem[1][len(elem.necessary_args_name):] 141 | if necessary_to_optional: 142 | for i in range(len(necessary_to_optional)): 143 | elem.optional_args[elem.default_optional_args_name[i]] = necessary_to_optional[i] 144 | for key in parsed_elem[2]: 145 | elem.optional_args[key] = parsed_elem[2][key] 146 | if GUI_CLASSES[parsed_elem[0]] in CONTAINER_ELEMENTS: 147 | elem.container = [TreeNode.make_tree_from_parsed_layout(elem.necessary_args["layout"])] 148 | 149 | return tree 150 | 151 | # Builds and returns a Tree structure from a string layout 152 | # The magic parsing of the string layout has it's own file to be able to contain all the fun xD 153 | @staticmethod 154 | def parse_string_layout(string_layout): 155 | parsed_layout = ps.parse_string_layout(string_layout) 156 | 157 | tree = TreeNode.make_tree_from_parsed_layout(parsed_layout) 158 | 159 | return tree 160 | 161 | # Converts value to string, needs its own function because string needs quotations to "string" 162 | @staticmethod 163 | def convert_to_str(value): 164 | if isinstance(value, str): 165 | return '"{}"'.format(value) 166 | else: 167 | return str(value) 168 | 169 | # Builds and returns a string layout from a Tree structure 170 | def layout_to_string(self): 171 | string_layout = "" 172 | 173 | if self.element in ("Root", "Row"): 174 | string_layout = "[{}]".format(", ".join([x.layout_to_string() for x in self.container])) 175 | else: 176 | args = [TreeNode.convert_to_str( 177 | self.necessary_args[arg_name]) if self.element not in CONTAINER_ELEMENTS or arg_name != "layout" 178 | else self.container[0].layout_to_string() 179 | for arg_name in self.necessary_args_name] 180 | 181 | kargs = ["{0}={1}".format(arg_name, TreeNode.convert_to_str(arg_value)) 182 | for arg_name, arg_value in self.optional_args.items() 183 | if self.optional_args[arg_name] != self.default_optional_args[arg_name]] 184 | 185 | string_layout = "sg.{elem_name}({args})".\ 186 | format(elem_name=self.element.__name__, args=", ".join((args + kargs))) 187 | 188 | return string_layout 189 | 190 | # Gets and creates all the necessary data structures with the argument info of the element 191 | # Also does that to get property tooltips from the Element's docs 192 | def set_properties(self): 193 | args_c = self.element.__init__.__code__.co_argcount 194 | args = self.element.__init__.__code__.co_varnames 195 | defaults = self.element.__init__.__defaults__ 196 | 197 | # parse argument tooltips from docs 198 | docs = self.element.__init__.__doc__ 199 | current_text = "" 200 | inside_param = False 201 | have_name = False 202 | current_name = "" 203 | for char in docs: 204 | if not inside_param: 205 | if char not in [" ", "\n", "\t", "\r"]: 206 | current_text += char 207 | if current_text == ":param": 208 | inside_param = True 209 | current_text = "" 210 | if char == "\n": 211 | current_text = "" 212 | else: 213 | current_text += char 214 | if not have_name: 215 | if char == ":": 216 | current_name = current_text[1:-1] 217 | current_text = "" 218 | have_name = True 219 | else: 220 | if char == "\n": 221 | self.args_docs[current_name] = current_text[:-1] 222 | current_text = "" 223 | inside_param = False 224 | have_name = False 225 | current_name = "" 226 | 227 | # print(self.args_docs) 228 | 229 | real_args = args[1:args_c] 230 | 231 | if not defaults: 232 | necessary_args = real_args 233 | def_args = [] 234 | else: 235 | necessary_args = real_args[:-len(defaults)] 236 | def_args = real_args[-len(defaults):] 237 | 238 | # print(real_args) 239 | # print(necessary_args) 240 | # print(def_args) 241 | 242 | for arg in necessary_args: 243 | self.necessary_args_name.append(arg) 244 | self.necessary_args[arg] = None 245 | 246 | for i, arg in enumerate(def_args): 247 | self.optional_args[arg] = defaults[i] 248 | self.default_optional_args_name.append(arg) 249 | 250 | # print(self.necessary_args) 251 | # print(self.optional_args) 252 | self.default_optional_args = {k: v for k, v in self.optional_args.items()} 253 | 254 | # Returns the layout that is shown on the right, with all the properties available for change for the element 255 | def get_properties_layout(self, elem_size, property_count): 256 | layout = [] 257 | 258 | layout.append([sg.Text(text="Necessary Args")]) 259 | 260 | for arg_name in self.necessary_args_name: 261 | if arg_name == "layout": 262 | continue 263 | value = str(self.necessary_args[arg_name] if not isinstance(self.necessary_args[arg_name], str) 264 | else '"' + self.necessary_args[arg_name] + '"') 265 | layout.append([sg.Text(text=arg_name, 266 | tooltip=(self.args_docs[arg_name] if arg_name in self.args_docs else None)), 267 | sg.Input(default_text=value, key=str(property_count) + "_" + arg_name)]) 268 | 269 | layout.append([sg.Text(text="Optional Args")]) 270 | 271 | for arg_name, arg_value in self.optional_args.items(): 272 | value = str(arg_value if not isinstance(arg_value, str) 273 | else '"' + arg_value + '"') 274 | layout.append([sg.Text(text=arg_name, 275 | tooltip=(self.args_docs[arg_name] if arg_name in self.args_docs else None)), 276 | sg.Input(default_text=value, key=str(property_count) + "_" + arg_name)]) 277 | 278 | return sg.Column(layout, scrollable=True, vertical_scroll_only=True, size=elem_size) 279 | 280 | # Applies the properties 281 | # Some day this might be automatic or provide a notice like "hey, you have unsaved properties, save?" 282 | # But today is not that day 283 | def apply_properties(self, values, property_count): 284 | for arg_name in self.necessary_args: 285 | key = str(property_count) + "_" + arg_name 286 | if key in values: 287 | self.necessary_args[arg_name] = ast.literal_eval(values[key]) 288 | 289 | for arg_name in self.optional_args: 290 | key = str(property_count) + "_" + arg_name 291 | if key in values: 292 | self.optional_args[arg_name] = ast.literal_eval(values[key]) 293 | 294 | # Receives a file and writes to it the template file with in layout in it 295 | # Dunno if this belongs here or in main, but it's here for now 296 | def write_to_file(self, file): 297 | file_text = '''import PySimpleGUI as sg 298 | 299 | # Template file taken from here https://pysimplegui.trinket.io/demo-programs#/demo-programs/the-basic-pysimplegui-program 300 | sg.theme('DarkAmber') # No gray windows please! 301 | 302 | # STEP 1 define the layout 303 | layout = ''' + self.layout_to_string() + ''' 304 | 305 | #STEP 2 - create the window 306 | window = sg.Window('Template Window', layout, resizable=True) 307 | # If you don't want to start with the window maximized comment this bellow vvv 308 | #window.maximize() 309 | 310 | # STEP3 - the event loop 311 | while True: 312 | event, values = window.read() # Read the event that happened and the values dictionary 313 | print(event, values) 314 | if event == sg.WIN_CLOSED or event == 'Exit': # If user closed window with X or if user clicked "Exit" button then exit 315 | break 316 | 317 | window.close()''' 318 | file.write(file_text) 319 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/README.md: -------------------------------------------------------------------------------- 1 | How to Use: 2 | =========== 3 | 4 | * Create and activate a virtual environment: 5 | 6 | virtualenv venv 7 | 8 | venv\Scripts\Activate 9 | 10 | * Install dependencies: 11 | 12 | pip install -r requirements.txt 13 | 14 | * Run the GUI demo: 15 | 16 | python main.py 17 | 18 | 19 | Note: 20 | ---- 21 | This Demo is taken from this repository and updated for python 3: 22 | https://github.com/amandeep511997/Text-Editor 23 | 24 | 25 | Sample: 26 | ---- 27 | Text Editor: 28 | 29 | ![Text Editor](text-editor.png) 30 | 31 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-center.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-justify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-justify.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-left.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-right.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/bold.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/copy.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/cut.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/find.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/font-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/font-color.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/highlight.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/italic.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/new.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/open.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/paste.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/redo.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/save.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/strike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/strike.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/underline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/underline.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/undo.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/requirements.txt: -------------------------------------------------------------------------------- 1 | pillow -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/tkinter_/text-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/text-editor.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/sample.gif -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.dist-info/ 48 | *.egg-info/ 49 | .installed.cfg 50 | *.egg 51 | 52 | # IntelliJ Idea family of suites 53 | .idea 54 | *.iml 55 | ## File-based project format: 56 | *.ipr 57 | *.iws 58 | ## mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # Briefcase log files 62 | logs/ 63 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/CHANGELOG: -------------------------------------------------------------------------------- 1 | # Toga Blog Post Release Notes 2 | 3 | ## 0.0.1 (18 Apr 2023) 4 | 5 | * Initial release 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2023, Alyssa Caples 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, this 12 | list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 26 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/README.rst: -------------------------------------------------------------------------------- 1 | Toga Blog Post 2 | ============== 3 | 4 | **This cross-platform app was generated by** `Briefcase`_ **- part of** 5 | `The BeeWare Project`_. **If you want to see more tools like Briefcase, please 6 | consider** `becoming a financial member of BeeWare`_. 7 | 8 | This will be the app for creating a blog post application 9 | 10 | .. _`Briefcase`: https://briefcase.readthedocs.io/ 11 | .. _`The BeeWare Project`: https://beeware.org/ 12 | .. _`becoming a financial member of BeeWare`: https://beeware.org/contributing/membership 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/pyproject.toml: -------------------------------------------------------------------------------- 1 | # This project was generated with Unknown using template: https://github.com/beeware/briefcase-template@v0.3.13 2 | [tool.briefcase] 3 | project_name = "Toga Blog Post" 4 | bundle = "com.example" 5 | version = "0.0.1" 6 | url = "https://example.com/togablogpost" 7 | license = "BSD license" 8 | author = "Alyssa Caples" 9 | author_email = "alyssa@depotanalytics.co" 10 | 11 | [tool.briefcase.app.togablogpost] 12 | formal_name = "Toga Blog Post" 13 | description = "This will be the app for creating a blog post application" 14 | long_description = """More details about the app should go here. 15 | """ 16 | icon = "src/togablogpost/resources/togablogpost" 17 | sources = [ 18 | "src/togablogpost", 19 | ] 20 | test_sources = [ 21 | "tests", 22 | ] 23 | 24 | requires = [ 25 | ] 26 | test_requires = [ 27 | "pytest", 28 | ] 29 | 30 | [tool.briefcase.app.togablogpost.macOS] 31 | requires = [ 32 | "toga-cocoa~=0.3.0", 33 | "std-nslog~=1.0.0" 34 | ] 35 | 36 | [tool.briefcase.app.togablogpost.linux] 37 | requires = [ 38 | "toga-gtk~=0.3.0", 39 | ] 40 | 41 | [tool.briefcase.app.togablogpost.linux.system.debian] 42 | system_requires = [ 43 | # Needed to compile pycairo wheel 44 | 'libcairo2-dev', 45 | # Needed to compile PyGObject wheel 46 | 'libgirepository1.0-dev', 47 | ] 48 | 49 | system_runtime_requires = [ 50 | # Needed to provide GTK 51 | "libgtk-3-0", 52 | # Needed to provide GI bindings to GTK 53 | "libgirepository-1.0-1", 54 | "gir1.2-gtk-3.0", 55 | # Needed to provide WebKit2 at runtime 56 | # "libwebkit2gtk-4.0-37", 57 | # "gir1.2-webkit2-4.0", 58 | ] 59 | 60 | [tool.briefcase.app.togablogpost.linux.system.rhel] 61 | system_requires = [ 62 | # Needed to compile pycairo wheel 63 | 'cairo-gobject-devel', 64 | # Needed to compile PyGObject wheel 65 | 'gobject-introspection-devel', 66 | ] 67 | 68 | system_runtime_requires = [ 69 | # Needed to support Python bindings to GTK 70 | "gobject-introspection", 71 | # Needed to provide GTK 72 | "gtk3", 73 | # Needed to provide WebKit2 at runtime 74 | # "webkit2gtk3", 75 | ] 76 | 77 | [tool.briefcase.app.togablogpost.linux.system.arch] 78 | system_requires = [ 79 | # Needed to compile pycairo wheel 80 | 'cairo', 81 | # Needed to compile PyGObject wheel 82 | 'gobject-introspection', 83 | ] 84 | 85 | system_runtime_requires = [ 86 | # Needed to provide GTK 87 | "gtk", 88 | # Needed to provide WebKit2 at runtime 89 | # "webkit2gtk", 90 | ] 91 | 92 | [tool.briefcase.app.togablogpost.linux.appimage] 93 | system_requires = [ 94 | "libcairo2-dev", 95 | "libgirepository1.0-dev", 96 | "libgtk-3-dev", 97 | "libpango1.0-dev", 98 | "librsvg2-dev", 99 | # Needed to support Webkit2 100 | # "gir1.2-webkit2-4.0", 101 | # "libwebkit2gtk-4.0-dev", 102 | ] 103 | linuxdeploy_plugins = [ 104 | "DEPLOY_GTK_VERSION=3 gtk", 105 | ] 106 | 107 | [tool.briefcase.app.togablogpost.linux.flatpak] 108 | flatpak_runtime = "org.gnome.Platform" 109 | flatpak_runtime_version = "42" 110 | flatpak_sdk = "org.gnome.Sdk" 111 | 112 | [tool.briefcase.app.togablogpost.windows] 113 | requires = [ 114 | "toga-winforms~=0.3.0", 115 | ] 116 | 117 | # Mobile deployments 118 | [tool.briefcase.app.togablogpost.iOS] 119 | requires = [ 120 | "toga-iOS~=0.3.0", 121 | "std-nslog~=1.0.0" 122 | ] 123 | 124 | [tool.briefcase.app.togablogpost.android] 125 | requires = [ 126 | "toga-android~=0.3.0" 127 | ] 128 | 129 | # Web deployments 130 | [tool.briefcase.app.togablogpost.web] 131 | requires = [ 132 | "toga-web~=0.3.0", 133 | ] 134 | style_framework = "Bootstrap v4.6" 135 | 136 | # 2023-02-26: This is a workaround for briefcase#1089/pyscript#1204. 137 | extra_pyscript_toml_content = """ 138 | [[runtimes]] 139 | src = "https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js" 140 | name = "Python runtime" 141 | lang = "python" 142 | """ 143 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__main__.py: -------------------------------------------------------------------------------- 1 | from togablogpost.app import main 2 | 3 | if __name__ == '__main__': 4 | main().main_loop() 5 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/app.py: -------------------------------------------------------------------------------- 1 | import toga 2 | from toga.style import Pack 3 | from toga.style.pack import COLUMN 4 | import random 5 | from pathlib import Path 6 | 7 | def read_in_words(): 8 | file_path = Path(__file__).parent / "english_words.txt" 9 | lines = [] 10 | with open(file_path, "r") as file: 11 | for line in file: 12 | lines.append(line.strip()) # add each line to the list, stripped of any whitespace 13 | return lines 14 | 15 | class Hangman(toga.App): 16 | valid_alphabet = set("abcdefghijklmnopqrstuvwxyz") 17 | all_words = read_in_words() 18 | 19 | #styles 20 | game_font = "Century Schoolbook" 21 | title_style = Pack(padding=(0, 5), font_family=game_font, font_size=20, text_align="center", font_weight="bold") 22 | button_style = Pack(padding=(0, 5), font_family=game_font, font_size=15, text_align="center") 23 | game_over_style = Pack(padding=(0, 5), font_family=game_font, font_size=50, text_align="center", font_weight="bold") 24 | secret_word_style = Pack(padding=(0, 5), font_family=game_font, font_size=35, text_align="center", font_weight="bold") 25 | 26 | def startup(self): 27 | self.secret_word = "" 28 | 29 | main_box = toga.Box(style=Pack(direction=COLUMN)) 30 | main_box.add(toga.Label("Welcome to Hangman!", style=Hangman.title_style )) 31 | main_box.add(toga.Button("Play Hangman" , style=Hangman.button_style, on_press=self.start_new_game)) 32 | main_box.add(toga.Button("Start Game with Own Word", style=Hangman.button_style, on_press=self.enter_own_word)) 33 | 34 | self.main_window = toga.MainWindow(title="Hangman", position=(100, 100), size=(800, 600)) 35 | self.main_window.content = main_box 36 | self.main_window.show() 37 | 38 | def enter_own_word(self, widget): 39 | box = toga.Box(style=Pack(direction=COLUMN)) 40 | box.add(toga.Label("Enter your secret word:", style=Hangman.title_style )) 41 | box.add(toga.TextInput(placeholder="Enter Your Word Here", on_change=self.update_secret_word_handler)) #could add valitors here if needed 42 | box.add(toga.Button("Start Game", style=Hangman.button_style, on_press=self.start_new_game)) 43 | self.main_window.content = box 44 | self.main_window.show() 45 | 46 | def start_new_game(self, widget): 47 | 48 | if widget.text == "Play Hangman" or widget.text == "Start a New Game": 49 | self.secret_word = self.get_word() # get a random word 50 | print(self.secret_word) 51 | 52 | #initialize variables used for each game 53 | self.letters = set(self.secret_word) 54 | self.used_letters = [] 55 | self.lives = 7 56 | self.correct_letters = set() 57 | self.hangman_image_path = str(Path(__file__).parent / "hangman_images" ) 58 | self.hangman_image = "/hangman-0" #set to beginning 59 | self.revealed_word = ["_"] * len(self.secret_word) 60 | self.game_state() 61 | 62 | def get_word(self): 63 | random_item = random.choice(Hangman.all_words) 64 | return random_item 65 | 66 | def game_state(self): 67 | box = toga.Box(style=Pack(direction=COLUMN)) 68 | 69 | #heading 70 | box.add(toga.Label("Guess a Letter!", style=Hangman.title_style )) 71 | box.add(toga.Label("Word: " + " ".join(self.revealed_word), style=Hangman.secret_word_style)) 72 | box.add(toga.TextInput(validators=[self.max_length, self.letters_only], on_change=self.process_letter)) 73 | 74 | # used letters + lives 75 | box.add(toga.Label("Used Letters: " + " ".join(self.used_letters), style=Hangman.title_style)) 76 | box.add(toga.Label("Lives: " + str(self.lives), style=Hangman.title_style)) 77 | 78 | #buttons 79 | box.add(toga.Button("Start a New Game", style=Hangman.button_style, on_press=self.start_new_game)) 80 | box.add(toga.Button("Play Hangman with Own Word", style=Hangman.button_style, on_press=self.enter_own_word)) 81 | 82 | #image 83 | box.add(toga.ImageView(image=self.hangman_image_path + self.hangman_image + ".PNG")) 84 | 85 | self.main_window.content = box 86 | 87 | def kill_game(self, widget): 88 | self.end_game(victory=False) 89 | 90 | def process_letter(self, widget): 91 | self.used_letters.append(widget.value) 92 | if widget.value in self.letters: #if letter is in word, replace underscore with letter 93 | self.correct_letters = self.correct_letters.union(widget.value) 94 | 95 | for i in range(len(self.secret_word)): 96 | if self.secret_word[i] in self.correct_letters: 97 | self.revealed_word[i] = self.secret_word[i] 98 | 99 | else: #else, remove lives & change image 100 | self.lives = self.lives - 1 101 | number = int(self.hangman_image[-1]) + 1 102 | self.hangman_image = self.hangman_image[:-1] + str(number) 103 | 104 | if self.lives == 0: #check if game is over 105 | self.end_game(victory=False) 106 | elif "_" not in self.revealed_word: 107 | self.end_game(victory=True) 108 | else: 109 | self.game_state() 110 | 111 | def end_game(self, victory=False): 112 | box = toga.Box(style=Pack(direction=COLUMN)) 113 | 114 | if victory: 115 | box.add(toga.Label("You won! :)", style=Hangman.game_over_style)) 116 | img_path = self.hangman_image_path + "/victory.PNG" 117 | else: 118 | box.add(toga.Label("You lost! :C", style=Hangman.game_over_style)) 119 | img_path = self.hangman_image_path + "/death.PNG" 120 | 121 | box.add(toga.Label("The word was: " + self.secret_word, style=Hangman.secret_word_style)) 122 | box.add(toga.Button("Start a New Game", style=Hangman.button_style, on_press=self.start_new_game)) 123 | box.add(toga.Button("Play Hangman with Own Word", style=Hangman.button_style, on_press=self.enter_own_word)) 124 | 125 | box.add(toga.ImageView(image=img_path)) 126 | self.main_window.content = box 127 | self.main_window.show() 128 | 129 | 130 | 131 | #save variable handler 132 | def update_secret_word_handler(self, widget): 133 | self.secret_word = widget.value 134 | 135 | # validators 136 | def letters_only(self, letter): 137 | lowercase = letter.lower() 138 | if lowercase in Hangman.valid_alphabet: 139 | return None 140 | else: 141 | return "Please enter a letter" 142 | 143 | def max_length(self, letter): 144 | if len(letter) > 1: 145 | return "Please enter only one letter" 146 | else: 147 | return None 148 | 149 | def main(): 150 | return Hangman() 151 | 152 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/english_words.txt: -------------------------------------------------------------------------------- 1 | abruptly 2 | absurd 3 | abyss 4 | affix 5 | askew 6 | avenue 7 | awkward 8 | axiom 9 | azure 10 | bagpipes 11 | bandwagon 12 | banjo 13 | bayou 14 | beekeeper 15 | bikini 16 | blitz 17 | blizzard 18 | boggle 19 | bookworm 20 | boxcar 21 | boxful 22 | buckaroo 23 | buffalo 24 | buffoon 25 | buxom 26 | buzzard 27 | buzzing 28 | buzzwords 29 | caliph 30 | cobweb 31 | cockiness 32 | croquet 33 | crypt 34 | curacao 35 | cycle 36 | daiquiri 37 | dirndl 38 | disavow 39 | dizzying 40 | duplex 41 | dwarves 42 | embezzle 43 | equip 44 | espionage 45 | euouae 46 | exodus 47 | faking 48 | fishhook 49 | fixable 50 | fjord 51 | flapjack 52 | flopping 53 | fluffiness 54 | flyby 55 | foxglove 56 | frazzled 57 | frizzled 58 | fuchsia 59 | funny 60 | gabby 61 | galaxy 62 | galvanize 63 | gazebo 64 | giaour 65 | gizmo 66 | glowworm 67 | glyph 68 | gnarly 69 | gnostic 70 | gossip 71 | grogginess 72 | haiku 73 | haphazard 74 | hyphen 75 | iatrogenic 76 | icebox 77 | injury 78 | ivory 79 | ivy 80 | jackpot 81 | jaundice 82 | jawbreaker 83 | jaywalk 84 | jazziest 85 | jazzy 86 | jelly 87 | jigsaw 88 | jinx 89 | jiujitsu 90 | jockey 91 | jogging 92 | joking 93 | jovial 94 | joyful 95 | juicy 96 | jukebox 97 | jumbo 98 | kayak 99 | kazoo 100 | keyhole 101 | khaki 102 | kilobyte 103 | kiosk 104 | kitsch 105 | kiwifruit 106 | klutz 107 | knapsack 108 | larynx 109 | lengths 110 | lucky 111 | luxury 112 | lymph 113 | marquis 114 | matrix 115 | megahertz 116 | microwave 117 | mnemonic 118 | mystify 119 | naphtha 120 | nightclub 121 | nowadays 122 | numbskull 123 | nymph 124 | onyx 125 | ovary 126 | oxidize 127 | oxygen 128 | pajama 129 | peekaboo 130 | phlegm 131 | pixel 132 | pizazz 133 | pneumonia 134 | polka 135 | pshaw 136 | psyche 137 | puppy 138 | puzzling 139 | quartz 140 | queue 141 | quips 142 | quixotic 143 | quiz 144 | quizzes 145 | quorum 146 | razzmatazz 147 | rhubarb 148 | rhythm 149 | rickshaw 150 | schnapps 151 | scratch 152 | shiv 153 | snazzy 154 | sphinx 155 | spritz 156 | squawk 157 | staff 158 | strength 159 | strengths 160 | stretch 161 | stronghold 162 | stymied 163 | subway 164 | swivel 165 | syndrome 166 | thriftless 167 | thumbscrew 168 | topaz 169 | transcript 170 | transgress 171 | transplant 172 | triphthong 173 | twelfth 174 | twelfths 175 | unknown 176 | unworthy 177 | unzip 178 | uptown 179 | vaporize 180 | vixen 181 | vodka 182 | voodoo 183 | vortex 184 | walkway 185 | waltz 186 | wave 187 | wavy 188 | waxy 189 | wellspring 190 | wheezy 191 | whiskey 192 | whizzing 193 | whomever 194 | wimpy 195 | witchcraft 196 | wizard 197 | woozy 198 | wristwatch 199 | wyvern 200 | xylophone 201 | yachtsman 202 | yippee 203 | yoked 204 | youthful 205 | yummy 206 | zephyr 207 | zigzag 208 | zigzagging 209 | zilch 210 | zipper 211 | zodiac 212 | zombie -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/death.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/death.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-0.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-0.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-1.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-2.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-3.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-4.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-5.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-6.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/victory.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/victory.PNG -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.icns -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.ico -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/test_app.py: -------------------------------------------------------------------------------- 1 | def test_first(): 2 | "An initial test for the app" 3 | assert 1 + 1 == 2 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/togablogpost.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import tempfile 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | 9 | def run_tests(): 10 | project_path = Path(__file__).parent.parent 11 | os.chdir(project_path) 12 | 13 | # Determine any args to pass to pytest. If there aren't any, 14 | # default to running the whole test suite. 15 | args = sys.argv[1:] 16 | if len(args) == 0: 17 | args = ["tests"] 18 | 19 | returncode = pytest.main( 20 | [ 21 | # Turn up verbosity 22 | "-vv", 23 | # Disable color 24 | "--color=no", 25 | # Overwrite the cache directory to somewhere writable 26 | "-o", 27 | f"cache_dir={tempfile.gettempdir()}/.pytest_cache", 28 | ] + args 29 | ) 30 | 31 | print(f">>>>>>>>>> EXIT {returncode} <<<<<<<<<<") 32 | 33 | 34 | if __name__ == "__main__": 35 | run_tests() 36 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/wxPython_/README.md: -------------------------------------------------------------------------------- 1 | How to Use: 2 | =========== 3 | 4 | * Create and activate a virtual environment: 5 | 6 | virtualenv venv 7 | 8 | venv\Scripts\Activate 9 | 10 | * Install dependencies: 11 | 12 | pip install -r requirements.txt 13 | 14 | * Run the GUI demo: 15 | 16 | python main.py 17 | 18 | 19 | Note: 20 | ---- 21 | This Demo is taken from this repository: 22 | https://github.com/Andereoo/WxPyBrowser 23 | 24 | Sample: 25 | ---- 26 | Web Browser: 27 | 28 | ![Web Browser](demo.gif) 29 | 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/wxPython_/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/wxPython_/demo.gif -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/wxPython_/main.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import wx.aui 3 | import wx.lib.platebtn 4 | import wx.html2 5 | import wx.lib.agw.aui.tabart 6 | 7 | 8 | class HistoryPage(wx.Panel): 9 | def __init__(self, parent, history_var): 10 | wx.Panel.__init__(self, parent=parent) 11 | 12 | self.open = True 13 | self.parent = parent 14 | self.history_var = history_var 15 | self.frame = wx.GetTopLevelParent(self) 16 | 17 | pagesizer = wx.BoxSizer(wx.VERTICAL) 18 | self.listbox = listbox = wx.ListBox(self) 19 | top_bar_container = wx.BoxSizer(wx.HORIZONTAL) 20 | label = wx.StaticText(self, label='Double-click on an item to open it.', style=wx.ST_ELLIPSIZE_END) 21 | new = wx.Button(self, label='+', size=(30, 30), style=wx.BORDER_NONE) 22 | new_tip = wx.ToolTip('Open a new tab') 23 | new.SetToolTip(new_tip) 24 | top_bar_container.Add(label, 1, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) 25 | top_bar_container.AddSpacer(30) 26 | top_bar_container.Add(new, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5) 27 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND) 28 | pagesizer.Add(listbox, proportion=True, flag=wx.EXPAND) 29 | new.Bind(wx.EVT_BUTTON, self.tab_new) 30 | self.Bind(wx.EVT_LISTBOX_DCLICK, self.open_link) 31 | 32 | self.SetSizer(pagesizer) 33 | 34 | self.refresh() 35 | 36 | def refresh(self): 37 | if self.open: 38 | try: 39 | self.listbox.AppendItems(self.history_var[len(self.listbox.GetItems()):]) 40 | wx.CallLater(3000, self.refresh) 41 | except: 42 | pass 43 | 44 | def open_link(self, event): 45 | page = WebPage(self.parent, self.history_var, url=event.GetString()) 46 | self.parent.AddPage(page, caption="Loading") 47 | 48 | def tab_new(self, event): 49 | page = WebPage(self.parent, self.history_var) 50 | self.parent.AddPage(page, caption="Loading", select=True) 51 | 52 | def on_close(self): 53 | self.open = False 54 | 55 | def on_select(self): 56 | self.frame.SetTitle("WxPyBrowser History") 57 | 58 | 59 | class SourceCode(wx.Panel): 60 | def __init__(self, parent, windowname, windowurl, history_var, *args, **kwargs): 61 | wx.Panel.__init__(self, parent) 62 | 63 | self.name = windowname 64 | self.parent = parent 65 | self.history_var = history_var 66 | self.frame = wx.GetTopLevelParent(self) 67 | 68 | pagesizer = wx.BoxSizer(wx.VERTICAL) 69 | self.source = source = wx.TextCtrl(self, *args, **kwargs) 70 | top_bar_container = wx.BoxSizer(wx.HORIZONTAL) 71 | label = wx.StaticText(self, label=('Source: '+windowurl), style=wx.ST_ELLIPSIZE_END) 72 | new = wx.Button(self, label='+', size=(30, 30), style=wx.BORDER_NONE) 73 | new_tip = wx.ToolTip('Open a new tab') 74 | new.SetToolTip(new_tip) 75 | top_bar_container.Add(label, 1, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) 76 | top_bar_container.AddSpacer(30) 77 | top_bar_container.Add(new, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5) 78 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND) 79 | pagesizer.Add(source, proportion=True, flag=wx.EXPAND) 80 | new.Bind(wx.EVT_BUTTON, self.tab_new) 81 | 82 | self.SetSizer(pagesizer) 83 | 84 | def on_select(self): 85 | self.frame.SetTitle(self.name) 86 | 87 | def tab_new(self, event): 88 | page = WebPage(self.parent, self.history_var) 89 | self.parent.AddPage(page, caption="Loading", select=True) 90 | 91 | def on_close(self): 92 | pass 93 | 94 | class WebPage(wx.Panel): 95 | def __init__(self, parent, history_var, url="https://duckduckgo.com/"): 96 | wx.Panel.__init__(self, parent=parent) 97 | 98 | self.parent = parent 99 | self.visited = history_var 100 | self.remember_history = True 101 | self.frame = wx.GetTopLevelParent(self) 102 | 103 | self.pagesizer = pagesizer = wx.BoxSizer(wx.VERTICAL) 104 | self.top_bar_container = top_bar_container = wx.FlexGridSizer(1, 8, 4, 4) 105 | self.back = back = wx.Button(self, label='<', size=(30, 30)) 106 | back_tip = wx.ToolTip('Go back one page') 107 | back.SetToolTip(back_tip) 108 | self.forward = forward = wx.Button(self, label='>', size=(30, 30)) 109 | forward_tip = wx.ToolTip('Go forward one page') 110 | forward.SetToolTip(forward_tip) 111 | self.reload = reload = wx.Button(self, label='⟳', size=(30, 30)) 112 | reload_tip = wx.ToolTip('Reload current page') 113 | reload.SetToolTip(reload_tip) 114 | self.url_field = url_field = wx.TextCtrl(self, style = wx.TE_PROCESS_ENTER) 115 | new = wx.Button(self, label='+', size=(30, 30)) 116 | new_tip = wx.ToolTip('Open a new tab') 117 | new.SetToolTip(new_tip) 118 | menu = wx.lib.platebtn.PlateButton(self, label='☰', size=(30, 30)) 119 | menu_tip = wx.ToolTip('Show menu') 120 | menu.SetToolTip(menu_tip) 121 | self.html_window = html_window = wx.html2.WebView.New(self) 122 | top_bar_container.AddGrowableCol(4) 123 | top_bar_container.Add(back, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5) 124 | top_bar_container.Add(forward, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5) 125 | top_bar_container.Add(reload, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5) 126 | top_bar_container.AddSpacer(30) 127 | top_bar_container.Add(url_field, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) 128 | top_bar_container.Add(menu, 0, wx.RIGHT | wx.BOTTOM | wx.TOP, 5) 129 | top_bar_container.AddSpacer(30) 130 | top_bar_container.Add(new, 0, wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT | wx.TOP, 5) 131 | 132 | self.find_container = find_container = wx.BoxSizer(wx.HORIZONTAL) 133 | self.find_field = find_field = wx.TextCtrl(self, style = wx.TE_PROCESS_ENTER) 134 | find_next = wx.Button(self, label='>', size=(30, 30)) 135 | find_next_tip = wx.ToolTip('Find next occurance') 136 | find_next.SetToolTip(find_next_tip) 137 | self.entire_word = entire_word = wx.CheckBox(self, label='Entire word') 138 | self.match_case = match_case = wx.CheckBox(self, label='Match case') 139 | self.highlight_results = highlight_results = wx.CheckBox(self, label='Highlight results') 140 | find_results_label = wx.StaticText(self) 141 | find_close = wx.Button(self, label='×', size=(30, 30)) 142 | find_close_tip = wx.ToolTip('Close find bar') 143 | find_close.SetToolTip(find_close_tip) 144 | find_container.Add(find_field, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5) 145 | find_container.Add(find_next, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) 146 | find_container.Add(entire_word, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) 147 | find_container.Add(match_case, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) 148 | find_container.Add(highlight_results, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) 149 | find_container.AddSpacer(30) 150 | find_container.Add(find_results_label, 1, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) 151 | find_container.Add(find_close, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5) 152 | 153 | self.zoom_container = zoom_container = wx.BoxSizer(wx.HORIZONTAL) 154 | self.zoom_slider = zoom_slider = wx.Slider(self, value=3, minValue=1, maxValue=5) 155 | zoom_close = wx.Button(self, label='×', size=(30, 30)) 156 | zoom_close_tip = wx.ToolTip('Close zoom bar') 157 | zoom_close.SetToolTip(zoom_close_tip) 158 | zoom_container.Add(zoom_slider, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5) 159 | zoom_container.AddSpacer(30) 160 | zoom_container.Add(zoom_close, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5) 161 | 162 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND) 163 | pagesizer.Add(html_window, proportion=True, flag=wx.EXPAND) 164 | pagesizer.Add(find_container, proportion=False, flag=wx.EXPAND) 165 | pagesizer.Add(zoom_container, proportion=False, flag=wx.EXPAND) 166 | pagesizer.Hide(find_container) 167 | pagesizer.Hide(zoom_container) 168 | 169 | find_field.Bind(wx.EVT_TEXT_ENTER, self.continue_find) 170 | find_next.Bind(wx.EVT_BUTTON, lambda event, goprev=True: self.continue_find(event, goprev)) 171 | entire_word.Bind(wx.EVT_CHECKBOX, self.continue_find) 172 | match_case.Bind(wx.EVT_CHECKBOX, self.continue_find) 173 | highlight_results.Bind(wx.EVT_CHECKBOX, self.continue_find) 174 | find_field.Bind(wx.EVT_TEXT, self.continue_find) 175 | find_close.Bind(wx.EVT_BUTTON, self.close_find) 176 | zoom_close.Bind(wx.EVT_BUTTON, self.close_zoom) 177 | zoom_slider.Bind(wx.EVT_COMMAND_SCROLL, self.zoom_slider_change) 178 | back.Bind(wx.EVT_BUTTON, self.tab_back) 179 | forward.Bind(wx.EVT_BUTTON, self.tab_foward) 180 | reload.Bind(wx.EVT_BUTTON, self.tab_reload) 181 | new.Bind(wx.EVT_BUTTON, self.tab_new) 182 | url_field.Bind(wx.EVT_TEXT_ENTER, self.loadpage) 183 | url_field.Bind(wx.EVT_SET_FOCUS, self.click_on_url_field) 184 | html_window.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.post_load_config) 185 | html_window.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.open_in_new_tab) 186 | html_window.Bind(wx.html2.EVT_WEBVIEW_TITLE_CHANGED, self.change_title) 187 | 188 | self.load_url(url) 189 | url_field.SetValue(url) 190 | 191 | settings_menu = wx.Menu() 192 | page_menu = wx.Menu() 193 | menu_zoom = wx.MenuItem(settings_menu, 0, "Change zoom") 194 | self.menu_contextmenu = menu_contextmenu = wx.MenuItem(settings_menu, 2, "Enable context menu", "", wx.ITEM_CHECK) 195 | self.menu_historyenabled = menu_historyenabled = wx.MenuItem(settings_menu, 3, "Remember page history", "", wx.ITEM_CHECK) 196 | menu_source = wx.MenuItem(page_menu, 0, "Show source") 197 | menu_history = wx.MenuItem(page_menu, 1, "Show history") 198 | menu_print = wx.MenuItem(page_menu, 2, "Print this page") 199 | menu_find = wx.MenuItem(page_menu, 3, "Find in page") 200 | settings_menu.Append(menu_zoom) 201 | settings_menu.AppendSeparator() 202 | try: 203 | self.html_window.EnableAccessToDevTools() 204 | self.menu_devtools = menu_devtools = wx.MenuItem(settings_menu, 1, "Enable access to dev tools", "", wx.ITEM_CHECK) 205 | settings_menu.Append(menu_devtools) 206 | menu_devtools.Check() 207 | settings_menu.Bind(wx.EVT_MENU, self.enable_devtools, menu_devtools) 208 | except: 209 | pass 210 | settings_menu.Append(menu_contextmenu) 211 | settings_menu.Append(menu_historyenabled) 212 | page_menu.Append(menu_source) 213 | page_menu.Append(menu_history) 214 | page_menu.AppendSeparator() 215 | page_menu.Append(menu_print) 216 | page_menu.Append(menu_find) 217 | page_menu.AppendSeparator() 218 | page_menu.AppendSubMenu(settings_menu, 'Page Settings') 219 | html_window.EnableContextMenu() 220 | html_window.EnableHistory() 221 | menu_contextmenu.Check() 222 | menu_historyenabled.Check() 223 | settings_menu.Bind(wx.EVT_MENU, self.enable_contextmenu, menu_contextmenu) 224 | settings_menu.Bind(wx.EVT_MENU, self.enable_historyenabled, menu_historyenabled) 225 | settings_menu.Bind(wx.EVT_MENU, self.adjust_zoom, menu_zoom) 226 | page_menu.Bind(wx.EVT_MENU, self.show_source, menu_source) 227 | page_menu.Bind(wx.EVT_MENU, self.show_history, menu_history) 228 | page_menu.Bind(wx.EVT_MENU, self.print_page, menu_print) 229 | page_menu.Bind(wx.EVT_MENU, self.find_in_page, menu_find) 230 | 231 | menu.SetMenu(page_menu) 232 | self.SetSizer(pagesizer) 233 | 234 | def show_source(self, event): 235 | title = self.html_window.GetCurrentTitle() 236 | if title == "": 237 | title = ("WxPyBrowser - Source for "+self.html_window.GetCurrentURL()) 238 | else: 239 | title = ("WxPyBrowser - Source for "+self.html_window.GetCurrentTitle()) 240 | page = SourceCode(self.parent, windowname=title, windowurl=self.html_window.GetCurrentURL(), history_var=self.visited, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL) 241 | page.source.SetValue(self.html_window.GetPageSource()) 242 | self.parent.AddPage(page, caption=title, select=False) 243 | 244 | def show_history(self, event): 245 | history_tab = HistoryPage(self.parent, self.visited) 246 | self.parent.AddPage(history_tab, caption="WxPyBrowser History", select=False) 247 | 248 | def adjust_zoom(self, event): 249 | self.pagesizer.Hide(self.find_container) 250 | self.pagesizer.Show(self.zoom_container) 251 | self.pagesizer.Layout() 252 | 253 | def close_zoom(self, event): 254 | self.pagesizer.Hide(self.zoom_container) 255 | self.pagesizer.Layout() 256 | 257 | def zoom_slider_change(self, event): 258 | scalenum = event.GetInt() 259 | scale_val = None 260 | if scalenum == 1: 261 | scale_val = wx.html2.WEBVIEW_ZOOM_TINY 262 | elif scalenum == 2: 263 | scale_val = wx.html2.WEBVIEW_ZOOM_SMALL 264 | elif scalenum == 3: 265 | scale_val = wx.html2.WEBVIEW_ZOOM_MEDIUM 266 | elif scalenum == 4: 267 | scale_val = wx.html2.WEBVIEW_ZOOM_LARGE 268 | elif scalenum == 5: 269 | scale_val = wx.html2.WEBVIEW_ZOOM_LARGEST 270 | 271 | self.html_window.SetZoom(scale_val) 272 | 273 | def print_page(self, event): 274 | self.html_window.Print() 275 | 276 | def find_in_page(self, event): 277 | self.pagesizer.Hide(self.zoom_container) 278 | self.pagesizer.Show(self.find_container) 279 | self.pagesizer.Layout() 280 | 281 | def continue_find(self, event, gonext=False): 282 | match_case = self.match_case.IsChecked() 283 | entire_word = self.entire_word.IsChecked() 284 | highlight_results = self.highlight_results.IsChecked() 285 | if not gonext: 286 | self.html_window.Find("") 287 | if match_case and entire_word and highlight_results: 288 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_ENTIRE_WORD | wx.html2.WEBVIEW_FIND_MATCH_CASE) 289 | elif match_case and highlight_results: 290 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_MATCH_CASE) 291 | elif entire_word and highlight_results: 292 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_ENTIRE_WORD) 293 | elif entire_word and match_case: 294 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_ENTIRE_WORD | wx.html2.WEBVIEW_FIND_MATCH_CASE) 295 | elif match_case: 296 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_MATCH_CASE) 297 | elif entire_word: 298 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_ENTIRE_WORD) 299 | elif highlight_results: 300 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT) 301 | else: 302 | self.html_window.Find(self.find_field.GetValue()) 303 | 304 | def close_find(self, event): 305 | self.pagesizer.Hide(self.find_container) 306 | self.pagesizer.Layout() 307 | 308 | def click_on_url_field(self, event): 309 | self.url_field.SelectAll() 310 | 311 | def enable_devtools(self, event): 312 | self.html_window.EnableAccessToDevTools(self.menu_devtools.IsChecked()) 313 | 314 | def enable_contextmenu(self, event): 315 | self.html_window.EnableContextMenu(self.menu_contextmenu.IsChecked()) 316 | 317 | def enable_historyenabled(self, event): 318 | history_enabled = self.menu_historyenabled.IsChecked() 319 | self.html_window.EnableHistory(history_enabled) 320 | self.remember_history = history_enabled 321 | 322 | if self.html_window.CanGoBack(): 323 | self.back.Enable() 324 | else: 325 | self.back.Disable() 326 | if self.html_window.CanGoForward(): 327 | self.forward.Enable() 328 | else: 329 | self.forward.Disable() 330 | 331 | def load_url(self, url=None): 332 | self.back.Disable() 333 | self.forward.Disable() 334 | self.reload.SetLabel("×") 335 | self.url_field.Disable() 336 | 337 | if url: 338 | self.html_window.LoadURL(url) 339 | 340 | def tab_back(self, event): 341 | self.load_url() 342 | self.html_window.GoBack() 343 | 344 | def tab_foward(self, event): 345 | self.load_url() 346 | self.html_window.GoForward() 347 | 348 | def tab_reload(self, event): 349 | if self.reload.GetLabel() == "×": 350 | self.html_window.Stop() 351 | self.post_load_config() 352 | else: 353 | self.load_url() 354 | self.html_window.Reload() 355 | 356 | def tab_new(self, event): 357 | page = WebPage(self.parent,self.visited) 358 | self.parent.AddPage(page, caption="Loading", select=True) 359 | 360 | def post_load_config(self, event=None): 361 | url = self.html_window.GetCurrentURL() 362 | 363 | self.url_field.SetValue(url) 364 | if self.parent.GetSelection() == self.parent.GetPageIndex(self): 365 | self.on_select() 366 | 367 | if self.html_window.CanGoBack(): 368 | self.back.Enable() 369 | if self.html_window.CanGoForward(): 370 | self.forward.Enable() 371 | self.reload.SetLabel("⟳") 372 | self.url_field.Enable() 373 | if self.remember_history: 374 | self.visited.append(url) 375 | 376 | def change_title(self, event): 377 | title = self.html_window.GetCurrentTitle() 378 | current_page_index = self.parent.GetPageIndex(self) 379 | self.parent.SetPageText(current_page_index, title) 380 | 381 | def on_select(self): 382 | title = self.html_window.GetCurrentTitle() 383 | divider = "" 384 | if title != "": 385 | divider = " - " 386 | self.frame.SetTitle("WxPyBrowser"+divider+title) 387 | 388 | def on_close(self): 389 | if self.html_window.IsBusy(): 390 | self.html_window.Stop() 391 | 392 | def open_in_new_tab(self, event): 393 | page = WebPage(self.parent, self.visited, url=event.URL) 394 | self.parent.AddPage(page, caption="Loading") 395 | 396 | def loadpage(self, event): 397 | url = self.url_field.GetValue() 398 | if (not url.startswith("https://") and not url.startswith("http://")): 399 | url = "https://"+url 400 | self.load_url(url) 401 | 402 | 403 | 404 | class Browser(wx.Frame): 405 | def __init__(self, parent, title): 406 | wx.Frame.__init__(self, parent, title=title) 407 | 408 | self.SetMinSize((900, 500)) 409 | 410 | self.history_closed = [] 411 | self.load_notebook() 412 | 413 | def load_notebook(self): 414 | self.panel = panel = wx.Panel(self) 415 | box = wx.BoxSizer(wx.HORIZONTAL) 416 | self.notebook = notebook = wx.aui.AuiNotebook(panel, style=wx.aui.AUI_NB_DEFAULT_STYLE) 417 | box.Add(notebook, proportion=True, flag=wx.EXPAND) 418 | panel.SetSizer(box) 419 | 420 | notebook.AddPage(WebPage(self.notebook, self.history_closed), caption="Loading", select=True) 421 | 422 | notebook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_page_close) 423 | notebook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_select) 424 | 425 | 426 | def on_page_close(self, event): 427 | event.Skip() 428 | try: 429 | page = self.notebook.GetCurrentPage() 430 | page.on_close() 431 | except: 432 | pass 433 | if self.notebook.GetPageCount() <= 1: 434 | self.Close() 435 | 436 | 437 | def on_page_select(self, event): 438 | self.notebook.GetCurrentPage().on_select() 439 | 440 | 441 | def main(): 442 | app = wx.App() 443 | browser = Browser(None, title='WxPyBrowser') 444 | browser.Show() 445 | app.MainLoop() 446 | 447 | 448 | if __name__ == '__main__': 449 | main() 450 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/wxPython_/requirements.txt: -------------------------------------------------------------------------------- 1 | wxpython -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/README.md: -------------------------------------------------------------------------------- 1 | How to Use: 2 | =========== 3 | 4 | * Create and activate a virtual environment: 5 | virtualenv venv 6 | 7 | venv\Scripts\Activate 8 | 9 | * Install dependencies: 10 | pip install -r requirements.txt 11 | 12 | * Run the GUI demo: 13 | python main.py --{%- if cookiecutter.gui_framework == 'PyQt5' %}pyqt5 14 | {% elif cookiecutter.gui_framework == 'PyQt6' %}pyqt6 15 | {% elif cookiecutter.gui_framework == 'PySide2' %}pyside2 16 | {% elif cookiecutter.gui_framework == 'PySide6' %}pyside6 17 | {%- endif %} 18 | 19 | 20 | Note: 21 | ---- 22 | This Demo is taken from this repository: 23 | https://github.com/UN-GCPDS/qt-material 24 | 25 | 26 | Sample: 27 | ---- 28 | Dark theme: 29 | ![dark](https://github.com/UN-GCPDS/qt-material/raw/master/docs/source/notebooks/_images/dark.gif) 30 | Light theme: 31 | ![light](https://github.com/UN-GCPDS/qt-material/raw/master/docs/source/notebooks/_images/light.gif) 32 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qt_material import list_themes 4 | 5 | themes = list_themes() 6 | themes = [t.replace('.xml', '') for t in themes] 7 | 8 | for theme in themes: 9 | os.system(f'python main.py --pyside6 {theme}') 10 | 11 | os.chdir('screenshots') 12 | 13 | commands = ( 14 | 'convert -delay 100 light_* light.gif', 15 | 'convert -delay 100 dark_* dark.gif', 16 | # 'rm ../../../docs/source/images/light.gif', 17 | # 'rm ../../../docs/source/images/dark.gif', 18 | # 'cp light.gif ../../../docs/source/images/light.gif', 19 | # 'cp dark.gif ../../../docs/source/images/dark.gif', 20 | # 'cp theme.png ../../../docs/source/images/theme.png', 21 | ) 22 | 23 | for command in commands: 24 | os.system(command) 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | from multiprocessing import freeze_support 5 | import psutil 6 | import signal 7 | import importlib.resources 8 | 9 | if '--pyside2' in sys.argv: 10 | from PySide2.QtWidgets import QApplication, QMainWindow, QFileDialog 11 | from PySide2.QtCore import QTimer, Qt, QCoreApplication 12 | from PySide2.QtGui import QIcon 13 | from PySide2.QtUiTools import QUiLoader 14 | 15 | elif '--pyside6' in sys.argv: 16 | from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog 17 | from PySide6.QtCore import QTimer, Qt, QCoreApplication 18 | from PySide6.QtGui import QIcon, QPixmap 19 | from PySide6.QtUiTools import QUiLoader 20 | 21 | elif '--pyqt5' in sys.argv: 22 | from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog 23 | from PyQt5.QtCore import QTimer, Qt, QCoreApplication 24 | from PyQt5 import uic, QtWebEngineWidgets 25 | from PyQt5.QtGui import QIcon 26 | 27 | elif '--pyqt6' in sys.argv: 28 | from PyQt6.QtWidgets import QApplication, QMainWindow, QFileDialog 29 | from PyQt6.QtCore import QTimer, Qt, QCoreApplication 30 | from PyQt6.QtGui import QIcon 31 | from PyQt6 import uic, QtWebEngineWidgets 32 | 33 | 34 | from qt_material import apply_stylesheet, QtStyleTools 35 | 36 | freeze_support() 37 | QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) 38 | 39 | app = QApplication([]) 40 | app.processEvents() 41 | app.setQuitOnLastWindowClosed(False) 42 | app.lastWindowClosed.connect(lambda: app.quit()) 43 | 44 | # Extra stylesheets 45 | extra = { 46 | 47 | # Button colors 48 | 'danger': '#dc3545', 49 | 'warning': '#ffc107', 50 | 'success': '#17a2b8', 51 | 52 | # Font 53 | 'font_family': 'Roboto', 54 | } 55 | 56 | 57 | ######################################################################## 58 | class RuntimeStylesheets(QMainWindow, QtStyleTools): 59 | # ---------------------------------------------------------------------- 60 | def __init__(self): 61 | """Constructor""" 62 | super().__init__() 63 | 64 | if '--pyside2' in sys.argv: 65 | self.main = QUiLoader().load('main_window.ui', self) 66 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PySide2') 67 | 68 | elif'--pyside6' in sys.argv: 69 | self.main = QUiLoader().load('main_window.ui', self) 70 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PySide6') 71 | 72 | elif '--pyqt5' in sys.argv: 73 | self.main = uic.loadUi('main_window.ui', self) 74 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PyQt5') 75 | 76 | elif '--pyqt6' in sys.argv: 77 | self.main = uic.loadUi('main_window.ui', self) 78 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PyQt6') 79 | 80 | else: 81 | logging.error( 82 | 'must include --pyside2, --pyside6 or --pyqt5 in args!') 83 | sys.exit() 84 | self.custom_styles() 85 | 86 | self.set_extra_colors(extra) 87 | self.add_menu_theme(self.main, self.main.menuStyles) 88 | self.show_dock_theme(self.main) 89 | 90 | logo = QIcon("qt_material:/logo/logo.svg") 91 | logo_frame = QIcon("qt_material:/logo/logo_frame.svg") 92 | 93 | self.main.setWindowIcon(logo) 94 | self.main.actionToolbar.setIcon(logo) 95 | [self.main.listWidget_2.item(i).setIcon(logo_frame) 96 | for i in range(self.main.listWidget_2.count())] 97 | 98 | # ---------------------------------------------------------------------- 99 | def custom_styles(self): 100 | """""" 101 | for i in range(self.main.toolBar_vertical.layout().count()): 102 | tool_button = self.main.toolBar_vertical.layout().itemAt(i).widget() 103 | tool_button.setMaximumWidth(150) 104 | tool_button.setMinimumWidth(150) 105 | 106 | 107 | T0 = 1000 108 | 109 | if __name__ == "__main__": 110 | 111 | # ---------------------------------------------------------------------- 112 | def take_screenshot(): 113 | pixmap = frame.main.grab() 114 | pixmap.save(os.path.join('screenshots', f'{theme}.png')) 115 | print(f'Saving {theme}') 116 | 117 | try: 118 | theme = sys.argv[2] 119 | QTimer.singleShot(T0, take_screenshot) 120 | QTimer.singleShot(T0 * 2, app.closeAllWindows) 121 | except: 122 | theme = 'default' 123 | 124 | # Set theme on in itialization 125 | apply_stylesheet(app, theme + '.xml', 126 | invert_secondary=( 127 | 'light' in theme and 'dark' not in theme), 128 | extra=extra) 129 | 130 | frame = RuntimeStylesheets() 131 | frame.main.show() 132 | 133 | app.exec_() 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/my_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1de9b6 4 | #6effe8 5 | #232629 6 | #4f5b62 7 | #31363b 8 | #000000 9 | #ffffff 10 | 11 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/requirements.txt: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.gui_framework == 'PyQt5' %}pyqt5 2 | psutil 3 | PyQtWebEngine 4 | qt_material 5 | {%- endif %} 6 | {%- if cookiecutter.gui_framework == 'PyQt6' %}pyqt6 7 | psutil 8 | PyQtWebEngine 9 | qt_material 10 | {%- endif %} 11 | {%- if cookiecutter.gui_framework == 'PySide2' %}PySide2 12 | psutil 13 | qt_material 14 | {%- endif %} 15 | {%- if cookiecutter.gui_framework == 'PySide6' %}PySide6 16 | psutil 17 | qt_material 18 | {%- endif %} -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = {{ cookiecutter.project_slug }} 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # {{ cookiecutter.project_slug }} documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import {{ cookiecutter.project_slug }} 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = '{{ cookiecutter.project_name }}' 50 | copyright = "{% now 'local', '%Y' %}, {{ cookiecutter.full_name }}" 51 | author = "{{ cookiecutter.full_name }}" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = {{ cookiecutter.project_slug }}.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = {{ cookiecutter.project_slug }}.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'alabaster' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = '{{ cookiecutter.project_slug }}doc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, '{{ cookiecutter.project_slug }}.tex', 131 | '{{ cookiecutter.project_name }} Documentation', 132 | '{{ cookiecutter.full_name }}', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, '{{ cookiecutter.project_slug }}', 142 | '{{ cookiecutter.project_name }} Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, '{{ cookiecutter.project_slug }}', 154 | '{{ cookiecutter.project_name }} Documentation', 155 | author, 156 | '{{ cookiecutter.project_slug }}', 157 | 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to {{ cookiecutter.project_name }}'s documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | {% if cookiecutter.create_author_file == 'y' -%}authors 14 | {% endif -%}history 15 | 16 | Indices and tables 17 | ================== 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install {{ cookiecutter.project_name }}, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install {{ cookiecutter.project_slug }} 16 | 17 | This is the preferred method to install {{ cookiecutter.project_name }}, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for {{ cookiecutter.project_name }} can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 51 | .. _tarball: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/tarball/master 52 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ={{ cookiecutter.project_slug }} 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use {{ cookiecutter.project_name }} in a project:: 6 | 7 | import {{ cookiecutter.project_slug }} 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/pyproject.toml: -------------------------------------------------------------------------------- 1 | {%- set license_classifiers = { 2 | 'MIT': 'License :: OSI Approved :: MIT License', 3 | 'BSD-3-Clause': 'License :: OSI Approved :: BSD License', 4 | 'ISC': 'License :: OSI Approved :: ISC License (ISCL)', 5 | 'Apache-2.0': 'License :: OSI Approved :: Apache Software License', 6 | 'GPL-3.0-only': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)' 7 | } -%} 8 | [tool] 9 | [tool.poetry] 10 | name = "{{ cookiecutter.project_slug }}" 11 | version = "{{ cookiecutter.version }}" 12 | homepage = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}" 13 | description = "Top-level package for {{ cookiecutter.project_name }}." 14 | authors = ["{{ cookiecutter.full_name.replace('\"', '\\\"') }} <{{ cookiecutter.email }}>"] 15 | readme = "README.rst" 16 | {%- if cookiecutter.open_source_license in license_classifiers %} 17 | license = "{{ cookiecutter.open_source_license }}" 18 | {%- endif %} 19 | classifiers=[ 20 | 'Development Status :: 2 - Pre-Alpha', 21 | 'Environment :: Win32 (MS Windows)', 22 | 'Environment :: MacOS X', 23 | 'Environment :: X11 Applications', 24 | 'Environment :: Web Environment', 25 | 'Environment :: Other Environment', 26 | 'Intended Audience :: Developers', 27 | 'Intended Audience :: End Users/Desktop', 28 | {%- if cookiecutter.open_source_license in license_classifiers %} 29 | '{{ license_classifiers[cookiecutter.open_source_license] }}', 30 | {%- endif %} 31 | 'Natural Language :: English', 32 | 'Programming Language :: Python :: 3', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.6', 35 | 'Programming Language :: Python :: 3.7', 36 | 'Programming Language :: Python :: 3.8', 37 | 'Programming Language :: Python :: 3.9', 38 | 'Topic :: Software Development', 39 | 'Topic :: Communications :: Email', 40 | 'Topic :: Office/Business', 41 | ] 42 | packages = [ 43 | { include = "{{ cookiecutter.project_slug }}" }, 44 | { include = "tests", format = "sdist" }, 45 | ] 46 | 47 | [tool.poetry.dependencies] 48 | python = "*" 49 | 50 | [tool.poetry.dev-dependencies] 51 | bumpversion = "*" 52 | coverage = "*" 53 | flake8 = "*" 54 | invoke = "*" 55 | isort = "*" 56 | pylint = "*" 57 | {% if cookiecutter.use_pytest == 'y' -%} 58 | pytest = "*"{% endif %} 59 | {%- if cookiecutter.gui_framework == 'PyQt5' %} 60 | PyQt5 = "*" 61 | {% elif cookiecutter.gui_framework == 'PyQt6' %} 62 | PyQt6 = "*" 63 | {% elif cookiecutter.gui_framework == 'PySide2' %} 64 | PySide2 = "*" 65 | {% elif cookiecutter.gui_framework == 'PySide6' %} 66 | PySide6 = "*" 67 | {% elif cookiecutter.gui_framework == 'Kivy' %} 68 | Kivy = "*" 69 | {% elif cookiecutter.gui_framework == 'wxPython' %} 70 | wxPython = "*" 71 | {% elif cookiecutter.gui_framework == 'Pyforms' %} 72 | Pyforms = "*" 73 | {% elif cookiecutter.gui_framework == 'PySimpleGUI' %} 74 | PySimpleGUI = "*" 75 | {% elif cookiecutter.gui_framework == 'PyGTK' %} 76 | PyGTK = "*"{%- endif %}sphinx = "*" 77 | tox = "*" 78 | yapf = "*" 79 | 80 | [tool.poetry.scripts] 81 | 82 | [build-system] 83 | requires = ["poetry>=0.12"] 84 | build-backend = "poetry.masonry.api" -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip>=19.2.3 2 | bump2version>=0.5.11 3 | wheel>=0.33.6 4 | watchdog>=0.9.0 5 | flake8>=3.7.8 6 | tox>=3.14.0 7 | coverage>=4.5.4 8 | Sphinx>=1.8.5 9 | twine>=1.14.0{% if cookiecutter.gui_framework == 'PyQt5' -%} 10 | PyQt5>=5.15.4{% endif %}{% if cookiecutter.gui_framework == 'PyQt6' -%} 11 | PyQt6>=6.1.1{% endif %}{% if cookiecutter.gui_framework == 'PySide2' -%} 12 | PySide2>=5.15.2{% endif %}{% if cookiecutter.gui_framework == 'PySide6' -%} 13 | PySide6>=6.1.2{% endif %}{% if cookiecutter.gui_framework == 'customTkinter' -%} 14 | customTkinter>=5.1.3{% endif %}{% if cookiecutter.gui_framework == 'Kivy' -%} 15 | Kivy>=2.0.0{% endif %}{% if cookiecutter.gui_framework == 'Toga' -%} 16 | toga>=0.3.1 17 | briefcase>=0.3.14{% endif %}{% if cookiecutter.gui_framework == 'wxPython' -%} 18 | wxPython>=4.1.1{% endif %}{% if cookiecutter.gui_framework == 'Pyforms' -%} 19 | Pyforms>=4.0.3{% endif %}{% if cookiecutter.gui_framework == 'PySimpleGUI' -%} 20 | PySimpleGUI>=4.45.0{% endif %}{% if cookiecutter.gui_framework == 'PyGTK' -%} 21 | PyGTK>=2.24.2{% endif %}{% if cookiecutter.use_pytest == 'y' -%} 22 | pytest>=6.2.4{% endif %} 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = {{ cookiecutter.version }} 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:{{ cookiecutter.project_slug }}/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | {%- if cookiecutter.use_pytest == 'y' %} 21 | [tool:pytest] 22 | collect_ignore = ['setup.py'] 23 | {%- endif %} 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.rst') as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open('HISTORY.rst') as history_file: 11 | history = history_file.read() 12 | 13 | requirements = [ 14 | {%- if cookiecutter.gui_framework == 'PyQt5' %}'PyQt5>=5.15.4', 15 | {% elif cookiecutter.gui_framework == 'PyQt6' %}'PyQt6>=6.1.1', 16 | {% elif cookiecutter.gui_framework == 'PySide2' %}'PySide2>=5.15.2', 17 | {% elif cookiecutter.gui_framework == 'PySide6' %}'PySide6>=6.1.2', 18 | {% elif cookiecutter.gui_framework == 'Kivy' %}'Kivy>=2.0.0', 19 | {% elif cookiecutter.gui_framework == 'wxPython' %}'wxPython>=4.1.1', 20 | {% elif cookiecutter.gui_framework == 'Pyforms' %}'Pyforms>=4.0.3', 21 | {% elif cookiecutter.gui_framework == 'PySimpleGUI' %}'PySimpleGUI>=4.45.0', 22 | {% elif cookiecutter.gui_framework == 'PyGTK' %}'PyGTK>=2.24.2', 23 | {%- endif %} 24 | ] 25 | 26 | test_requirements = [{%- if cookiecutter.use_pytest == 'y' %}'pytest>=3',{%- endif %} ] 27 | 28 | {%- set license_classifiers = { 29 | 'MIT license': 'License :: OSI Approved :: MIT License', 30 | 'BSD license': 'License :: OSI Approved :: BSD License', 31 | 'ISC license': 'License :: OSI Approved :: ISC License (ISCL)', 32 | 'Apache Software License 2.0': 'License :: OSI Approved :: Apache Software License', 33 | 'GNU General Public License v3': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)' 34 | } %} 35 | 36 | setup( 37 | author="{{ cookiecutter.full_name.replace('\"', '\\\"') }}", 38 | author_email='{{ cookiecutter.email }}', 39 | python_requires='>=3.6', 40 | classifiers=[ 41 | 'Development Status :: 2 - Pre-Alpha', 42 | 'Environment :: Win32 (MS Windows)', 43 | 'Environment :: MacOS X', 44 | 'Environment :: X11 Applications', 45 | 'Environment :: Web Environment', 46 | 'Environment :: Other Environment', 47 | 'Intended Audience :: Developers', 48 | 'Intended Audience :: End Users/Desktop', 49 | {%- if cookiecutter.open_source_license in license_classifiers %} 50 | '{{ license_classifiers[cookiecutter.open_source_license] }}', 51 | {%- endif %} 52 | 'Natural Language :: English', 53 | 'Programming Language :: Python :: 3', 54 | 'Programming Language :: Python :: 3.6', 55 | 'Programming Language :: Python :: 3.7', 56 | 'Programming Language :: Python :: 3.8', 57 | 'Programming Language :: Python :: 3.9', 58 | 'Topic :: Software Development', 59 | 'Topic :: Communications :: Email', 60 | 'Topic :: Office/Business', 61 | ], 62 | description="{{ cookiecutter.project_short_description }}", 63 | install_requires=requirements, 64 | {%- if cookiecutter.open_source_license in license_classifiers %} 65 | license="{{ cookiecutter.open_source_license }}", 66 | {%- endif %} 67 | long_description=readme + '\n\n' + history, 68 | include_package_data=True, 69 | keywords='{{ cookiecutter.project_slug }}', 70 | name='{{ cookiecutter.project_slug }}', 71 | packages=find_packages(include=['{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_slug }}.*']), 72 | test_suite='tests', 73 | tests_require=test_requirements, 74 | url='https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}', 75 | version='{{ cookiecutter.version }}', 76 | zip_safe=False, 77 | ) 78 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for {{ cookiecutter.project_slug }}.""" 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, flake8 3 | 4 | [travis] 5 | python = 6 | 3.8: py38 7 | 3.7: py37 8 | 3.6: py36 9 | 10 | [testenv:flake8] 11 | basepython = python 12 | deps = flake8 13 | commands = flake8 {{ cookiecutter.project_slug }} tests 14 | 15 | [testenv] 16 | setenv = 17 | PYTHONPATH = {toxinidir} 18 | {% if cookiecutter.use_pytest == 'y' -%} 19 | deps = 20 | -r{toxinidir}/requirements_dev.txt 21 | ; If you want to make tox run the tests with the same versions, create a 22 | ; requirements.txt with the pinned versions and uncomment the following line: 23 | ; -r{toxinidir}/requirements.txt 24 | commands = 25 | pip install -U pip 26 | pytest --basetemp={envtmpdir} 27 | {% else %} 28 | commands = python setup.py test 29 | {%- endif %} 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for {{ cookiecutter.project_name }}.""" 2 | 3 | __author__ = """{{ cookiecutter.full_name }}""" 4 | __email__ = '{{ cookiecutter.email }}' 5 | __version__ = '{{ cookiecutter.version }}' 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py: -------------------------------------------------------------------------------- 1 | """Main module.""" 2 | 3 | {%- if cookiecutter.gui_framework == 'PyQt5' %} 4 | 5 | import sys 6 | from PyQt5.QtWidgets import QApplication, QWidget 7 | 8 | 9 | def main(): 10 | 11 | app = QApplication(sys.argv) 12 | 13 | w = QWidget() 14 | w.setWindowTitle('{{ cookiecutter.project_name }}') 15 | w.show() 16 | 17 | sys.exit(app.exec_()) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | 23 | 24 | {%- endif %} --------------------------------------------------------------------------------