├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── emulator_request.md │ └── request_feature.yml ├── dependabot.yml └── workflows │ ├── deploy_website.yml │ ├── release_app.yml │ ├── test_build_app.yml │ └── test_build_website.yml ├── .gitignore ├── CNAME ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── announcements.json ├── requirements.in ├── requirements.txt ├── src ├── assets │ ├── fonts │ │ └── haxrcorp-4089 │ │ │ ├── haxrcorp-4089.ttf │ │ │ ├── license.txt │ │ │ └── readme.txt │ ├── images │ │ ├── discord_icon.png │ │ ├── dolphin_banner_dark.png │ │ ├── dolphin_banner_light.png │ │ ├── dolphin_logo.png │ │ ├── emuhaven_logo.ico │ │ ├── emuhaven_logo.png │ │ ├── github-mark-white.png │ │ ├── github-mark.png │ │ ├── kofi_button_blue.png │ │ ├── kofi_button_red.png │ │ ├── kofi_icon.png │ │ ├── placeholder_icon.png │ │ ├── play_dark.png │ │ ├── play_light.png │ │ ├── ryujinx_banner-original.png │ │ ├── ryujinx_banner.png │ │ ├── ryujinx_logo.png │ │ ├── settings_dark.png │ │ ├── settings_light.png │ │ ├── xenia_banner.png │ │ ├── xenia_canary_banner.png │ │ ├── xenia_logo.png │ │ ├── yuzu_early_access.png │ │ ├── yuzu_logo.png │ │ └── yuzu_mainline.png │ └── themes │ │ ├── LICENSE │ │ ├── anthracite.json │ │ ├── autumn.json │ │ ├── breeze.json │ │ ├── carrot.json │ │ ├── cherry.json │ │ ├── cobalt.json │ │ ├── coffee.json │ │ ├── dayn-night.json │ │ ├── extreme.json │ │ ├── flipper-zero.json │ │ ├── ghost-train.json │ │ ├── greengage.json │ │ ├── grey-ghost.json │ │ ├── hacked.json │ │ ├── hades.json │ │ ├── harlequin.json │ │ ├── lavender.json │ │ ├── marsh.json │ │ ├── metal.json │ │ ├── midnight.json │ │ ├── moonlit-sky.json │ │ ├── neon-banana.json │ │ ├── night-train.json │ │ ├── oceanix.json │ │ ├── orange.json │ │ ├── orangish.json │ │ ├── patina.json │ │ ├── pink.json │ │ ├── red.json │ │ ├── rime.json │ │ ├── rose.json │ │ ├── sky.json │ │ ├── sweetkind.json │ │ ├── test-card.json │ │ ├── trojan-blue.json │ │ ├── violet.json │ │ └── yellow.json ├── core │ ├── config │ │ ├── assets.py │ │ ├── cache.py │ │ ├── constants.py │ │ ├── paths.py │ │ ├── settings.py │ │ └── versions.py │ ├── emulators │ │ ├── dolphin │ │ │ ├── runner.py │ │ │ └── settings.py │ │ ├── ryujinx │ │ │ ├── runner.py │ │ │ └── settings.py │ │ ├── switch_emulator.py │ │ ├── xenia │ │ │ ├── runner.py │ │ │ └── settings.py │ │ └── yuzu │ │ │ ├── runner.py │ │ │ └── settings.py │ ├── logging │ │ └── logger.py │ ├── network │ │ ├── github.py │ │ ├── myrient.py │ │ └── web.py │ └── utils │ │ ├── files.py │ │ └── progress_handler.py ├── gui │ ├── emuhaven.py │ ├── frames │ │ ├── dolphin │ │ │ ├── dolphin_frame.py │ │ │ └── dolphin_games_frame.py │ │ ├── emulator_frame.py │ │ ├── firmware_keys_frame.py │ │ ├── game_list_frame.py │ │ ├── my_games_frame.py │ │ ├── my_switch_games_frame.py │ │ ├── myrient_game_list_frame.py │ │ ├── ryujinx │ │ │ ├── ryujinx_frame.py │ │ │ └── ryujinx_games_frame.py │ │ ├── settings │ │ │ ├── app_settings_frame.py │ │ │ ├── dolphin_settings_frame.py │ │ │ ├── ryujinx_settings_frame.py │ │ │ ├── setting_modal.py │ │ │ ├── settings_frame.py │ │ │ ├── xenia_settings_frame.py │ │ │ └── yuzu_settings_frame.py │ │ ├── xenia │ │ │ ├── xenia_frame.py │ │ │ └── xenia_games_frame.py │ │ └── yuzu │ │ │ ├── yuzu_frame.py │ │ │ └── yuzu_games_frame.py │ ├── handlers │ │ ├── progress │ │ │ ├── progress_frame.py │ │ │ ├── progress_handler.py │ │ │ └── progress_window.py │ │ └── thread_event_manager.py │ ├── libs │ │ ├── CTkMessagebox │ │ │ └── messagebox.py │ │ └── CTkScrollableDropdown │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── ctk_scrollable_dropdown.py │ │ │ └── ctk_scrollable_dropdown_frame.py │ └── windows │ │ ├── folder_selector.py │ │ ├── github_login_window.py │ │ ├── path_dialog.py │ │ └── saves_browser.py └── main.py └── website ├── .gitignore ├── README.md ├── babel.config.js ├── blog ├── 2024-09-18-v0.14.0.mdx ├── authors.yml └── tags.yml ├── docs ├── index.mdx └── intro.md ├── docusaurus.config.ts ├── package-lock.json ├── package.json ├── sidebars.ts ├── src ├── components │ └── HomepageFeatures │ │ ├── index.tsx │ │ └── styles.module.css ├── css │ └── custom.css └── pages │ ├── index.module.css │ ├── index.tsx │ └── markdown-page.md ├── static ├── .nojekyll └── img │ ├── docusaurus-social-card.jpg │ ├── docusaurus.png │ ├── favicon.ico │ ├── logo.svg │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | ko_fi: Viren070 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report a bug in EmuHaven 3 | labels: [Bug] 4 | body: 5 | 6 | - type: textarea 7 | id: reproduce-steps 8 | attributes: 9 | label: Steps to reproduce 10 | description: Provide an example of the issue. 11 | placeholder: | 12 | Example: 13 | 1. First step 14 | 2. Second step 15 | 3. Issue here 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | id: expected-behavior 21 | attributes: 22 | label: Expected behavior 23 | description: Explain what you should expect to happen. 24 | placeholder: | 25 | Example: 26 | "This should happen..." 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: actual-behavior 32 | attributes: 33 | label: Actual behavior 34 | description: Explain what actually happens. 35 | placeholder: | 36 | Example: 37 | "This happened instead..." 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | id: exceptions 43 | attributes: 44 | label: Errors 45 | description: | 46 | If you're seeing an exception raised in the console, share the error here. 47 | placeholder: | 48 | You can paste the error output in the console 49 | 50 | - type: input 51 | id: emuhaven-version 52 | attributes: 53 | label: EmuHaven version 54 | description: You can find your EmuHaven version in the top left of the screen 55 | placeholder: | 56 | Example: "0.14.0" 57 | validations: 58 | required: true 59 | 60 | - type: input 61 | id: operating-system 62 | attributes: 63 | label: Operating System 64 | description: List your Operating System 65 | placeholder: | 66 | Example: "Windows" 67 | validations: 68 | required: true 69 | 70 | - type: textarea 71 | id: other-details 72 | attributes: 73 | label: Other details 74 | placeholder: | 75 | Additional details and attachments. 76 | 77 | - type: checkboxes 78 | id: acknowledgements 79 | attributes: 80 | label: Acknowledgements 81 | description: Read this carefully, we will close and ignore your issue if you skimmed through this. 82 | options: 83 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. 84 | required: true 85 | - label: I have written a short but informative title. 86 | required: true 87 | - label: I have updated the app to version **[0.14.0](https://github.com/Viren070/EmuHaven/releases/latest)**. 88 | required: true 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/emulator_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Emulator Request 3 | about: Create a emulator request to be added to the app 4 | title: '' 5 | labels: 'emulator request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Emulator Details** 11 | 12 | Provide the following information 13 | 1. Emulator Name 14 | 2. Link to download 15 | 3. What does it emulate? 16 | 4. Provide the default paths, if there are any: 17 | - Default Install Path: 18 | - Default User Data Path: 19 | 6. Any required files? (bios, firmware etc.) 20 | 7. Supported file extensions for ROMs 21 | 8. Website that has downloads for ROMs available, if any exists. 22 | 23 | 24 | **Additional info** 25 | 26 | Add any other info about the emulator here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request_feature.yml: -------------------------------------------------------------------------------- 1 | name: ⭐ Feature request 2 | description: Suggest a feature to improve EmuHaven 3 | labels: [Feature request] 4 | body: 5 | 6 | - type: textarea 7 | id: feature-description 8 | attributes: 9 | label: Describe your suggested feature 10 | description: How can EmuHaven be improved? 11 | placeholder: | 12 | Example: 13 | "It should work like this..." 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | id: other-details 19 | attributes: 20 | label: Other details 21 | placeholder: | 22 | Additional details and attachments. 23 | 24 | - type: checkboxes 25 | id: acknowledgements 26 | attributes: 27 | label: Acknowledgements 28 | description: Read this carefully, we will close and ignore your issue if you skimmed through this. 29 | options: 30 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. 31 | required: true 32 | - label: I have written a short but informative title. 33 | required: true 34 | - label: I have updated the app to version **[0.14.0](https://github.com/Viren070/EmuHaven/releases/latest)**. 35 | required: true 36 | - label: I will fill out all of the requested information in this form. 37 | required: true 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: pip 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | python-packages: 14 | patterns: 15 | - "*" 16 | - package-ecosystem: "npm" # See documentation for possible values 17 | directory: "website/" # Location of package manifests 18 | schedule: 19 | interval: "daily" 20 | open-pull-requests-limit: 20 21 | groups: 22 | docusaurus: 23 | applies-to: version-updates 24 | dependency-type: production 25 | patterns: 26 | - "@docusaurus*" 27 | update-types: 28 | - "minor" 29 | - "patch" 30 | docusaurusDev: 31 | applies-to: version-updates 32 | dependency-type: development 33 | patterns: 34 | - "@docusaurus*" 35 | update-types: 36 | - "minor" 37 | - "patch" 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/deploy_website.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Docusaurus 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'website/**' 9 | workflow_dispatch: 10 | 11 | # Review gh actions docs if you want to further define triggers, paths, etc 12 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 13 | 14 | jobs: 15 | build: 16 | name: Build Docusaurus 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: website 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 18 28 | cache: npm 29 | cache-dependency-path: website/package-lock.json 30 | 31 | - name: Install dependencies 32 | run: npm ci 33 | working-directory: website 34 | - name: Build website 35 | run: npm run build 36 | working-directory: website 37 | 38 | - name: Upload Build Artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: website/build 42 | 43 | deploy: 44 | name: Deploy to GitHub Pages 45 | needs: build 46 | 47 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 48 | permissions: 49 | pages: write # to deploy to Pages 50 | id-token: write # to verify the deployment originates from an appropriate source 51 | 52 | # Deploy to the github-pages environment 53 | environment: 54 | name: github-pages 55 | url: ${{ steps.deployment.outputs.page_url }} 56 | 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Deploy to GitHub Pages 60 | id: deployment 61 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/release_app.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to upload assets to' 11 | required: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [windows-latest, macos-latest, ubuntu-latest] 19 | python-version: [3.12.7] 20 | name: Build App on ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | repository: Viren070/EmuHaven 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | cache: 'pip' 31 | 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install -r requirements.txt 36 | pip install pyinstaller 37 | 38 | 39 | - name: Determine customtkinter path 40 | id: determine-path 41 | run: | 42 | echo "CUSTOMTKINTER_PATH=$(python -c 'import customtkinter; print(customtkinter.__path__[0])')" >> $GITHUB_ENV 43 | shell: bash 44 | 45 | - name: Build distributables 46 | run: | 47 | if [ -z "${{ env.CUSTOMTKINTER_PATH }}" ]; then 48 | echo "CUSTOMTKINTER_PATH=${{ env.Python_ROOT_DIR }}/Lib/site-packages/customtkinter" >> $GITHUB_ENV 49 | fi 50 | pyinstaller --noconfirm --onedir --console --name "EmuHaven" --clean --add-data="${{ env.CUSTOMTKINTER_PATH }}:customtkinter/" --add-data="src/assets:assets/" src/main.py 51 | shell: bash 52 | 53 | - name: Create release archive 54 | run: | 55 | if [ "${{ matrix.os }}" == "windows-latest" ]; then 56 | powershell -command "Compress-Archive -Path 'dist/EmuHaven/*' -DestinationPath 'dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-win_x64.zip'" 57 | elif [ "${{ matrix.os }}" == "macos-latest" ]; then 58 | zip -r "dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-macos_x64.zip" dist/EmuHaven 59 | else 60 | zip -r "dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-linux_x64.zip" dist/EmuHaven 61 | fi 62 | shell: bash 63 | 64 | - uses: "softprops/action-gh-release@v2" 65 | env: 66 | TAG: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }} 67 | with: 68 | prerelease: ${{ contains(env.TAG, 'a') || contains(env.TAG, 'b') }} 69 | tag_name: ${{ env.TAG }} 70 | files: | 71 | dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-win_x64.zip 72 | dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-macos_x64.zip 73 | dist/EmuHaven-${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}-linux_x64.zip -------------------------------------------------------------------------------- /.github/workflows/test_build_app.yml: -------------------------------------------------------------------------------- 1 | name: Test Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'src/**' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | 14 | jobs: 15 | build: 16 | name: Test Build Windows App 17 | runs-on: windows-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | repository: Viren070/EmuHaven 22 | 23 | - name: Set up Python 3.12 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: 3.12.6 27 | cache: 'pip' 28 | 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install -r requirements.txt 33 | pip install pyinstaller 34 | 35 | - name: Build distributables 36 | run: | 37 | pyinstaller --noconfirm --onedir --console --name "EmuHaven" --clean --add-data "${{ env.Python_ROOT_DIR }}/Lib/site-packages/customtkinter;customtkinter/" --add-data "src/assets;assets/" src/main.py 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/test_build_website.yml: -------------------------------------------------------------------------------- 1 | name: Test Build Website 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | # Review gh actions docs if you want to further define triggers, paths, etc 10 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 11 | 12 | jobs: 13 | build: 14 | name: Test Build Docusaurus 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | working-directory: website 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | cache: npm 27 | cache-dependency-path: website/package-lock.json 28 | 29 | - name: Install dependencies 30 | run: npm ci 31 | working-directory: website 32 | - name: Build website 33 | run: npm run build 34 | working-directory: website 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | Emulator Manager Releases/ 29 | create-release.bat 30 | Emulator Files 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ 164 | 165 | # VS Code 166 | .vscode*/ 167 | # App specific folders 168 | User Data/ 169 | 170 | # Other 171 | update-requirements.bat 172 | old_src/ 173 | testing.py 174 | src/testing.py 175 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | emuhaven.viren070.me -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | anything 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Viren070 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## For 0.11 2 | 3 | - [x] Minimise PathDialog when browse is clicked 4 | - [x] adjust fg colour for root window 5 | - [x] Adjust fg, bg colour values and border widths for PathDialog 6 | - [x] Fix search for current ROMS 7 | - [x] Fix duplication of last ROM on any page after searching for downloading 8 | 9 | ## For 0.12 10 | 11 | - [x] Make menubar on LHS scrollable 12 | - [x] Don't update text to include version if text has been altered 13 | - [x] Move `Use Yuzu Installer` setting to yuzu settings 14 | - [x] Redesign firmware and key install section and add dropdown menu to select version and show installed version 15 | - [x] Rewrite or delete firmware downloader 16 | - [x] Use Dolphin website for dolphin downloads and add ability to switch from beta and development channels. 17 | - [x] factor out common switch emulator functions from yuzu into switch emulator 18 | - [x] Write firmware and key version to metadata 19 | - [x] Add Ryujinx Support 20 | - [x] Add ryujinx.py 21 | - [x] Add ryujinx_frame.py 22 | - [x] Add ryujinx_settings_frame.py 23 | - [x] Add ryujinx_settings.py 24 | 25 | - [x] Add base EmulatorFrame class that all other emulator_frame classes inherit from. 26 | - [x] More accurate download speed by averaging across set amount of intervals and not since starting time 27 | 28 | ## For 0.13 29 | - [x] Add Xenia support for both master and canary builds. 30 | - [ ] redesign "My ROMS" section for yuzu and ryujinx 31 | 32 | - Use cache\game_list\ for yuzu 33 | - use games for ryujinx 34 | - use https://github.com/arch-box/titledb for covers 35 | - https://new.mirror.lewd.wtf/archive/nintendo/switch/savegames/ for saves 36 | - for gamebanana: 37 | - use `https://api.gamebanana.com/Core/List/Like?itemtype=Game&field=name&match={match}` 38 | - where match is the name of the game with non-ASCII characters removed and spaces replaced with % to get the ID of the game. 39 | - then use `https://gamebanana.com/apiv10/Mod/Index?_nPage=1&_nPerpage=50&_sSort=Generic_MostDownloaded&_aFilters[Generic_Game]={id}` where id is the ID that was just found 40 | 41 | 42 | - [x] Add shortcuts for emulators which will show window with progress bar for updating. 43 | - basically just add CLI support 44 | - Add shortcut setting to each emulator. User provides path. If left empty or changed to empty, disable and/or remove shortcut. 45 | - Add new updater window and logic to handle launching this window and updating emulator through arguments 46 | - [x] Add option to use current directory for settings and metadata instead of attempting to use %appdata%\Roaming\Emulator Manager 47 | 48 | - [x] Add custom option for import/exports. remove 'exclude nand and keys' 49 | - [x] add option `check for update at start-up` that will control whether or not the app will check for an update at start-up 50 | - [ ] Refactor cache implementation to reduce memory usage for ROM menus. 51 | 52 | 53 | ## For 0.14 54 | - [ ] Implement mod downloading 55 | - [ ] Implement game downloading for switch (searching websites, not providing direct download) 56 | - [ ] Auto select play menu when selecting an emulator 57 | 58 | 59 | ## Emulators to add 60 | - [ ] Add Xemu support 61 | - [ ] Add Cemu support 62 | - [ ] Add Citra support (pablomk7) 63 | - [ ] Add Duckstation support 64 | - [ ] Add PCSX2 65 | - [ ] Add PPSSPP support 66 | 67 | ## other 68 | 69 | - [x] consider removing export directory setting and instead ask user for each export 70 | 71 | 72 | -------------------------------------------------------------------------------- /announcements.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "title": "Welcome", 4 | "message": "Welcome to EmuHaven! This is a test announcement." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.12.3 2 | CTkMessagebox==2.7 3 | CTkMessagebox==2.7 4 | CTkToolTip==0.8 5 | CTkToolTip==0.8 6 | customtkinter==5.2.2 7 | packaging==24.1 8 | Pillow==10.4.0 9 | platformdirs==4.3.6 10 | py7zr==0.22.0 11 | Requests==2.32.3 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.12 3 | # by the following command: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | beautifulsoup4==4.12.3 8 | # via -r requirements.in 9 | brotli==1.1.0 10 | # via py7zr 11 | certifi==2024.8.30 12 | # via requests 13 | charset-normalizer==3.3.2 14 | # via requests 15 | ctkmessagebox==2.7 16 | # via -r requirements.in 17 | ctktooltip==0.8 18 | # via -r requirements.in 19 | customtkinter==5.2.2 20 | # via 21 | # -r requirements.in 22 | # ctkmessagebox 23 | darkdetect==0.8.0 24 | # via customtkinter 25 | idna==3.8 26 | # via requests 27 | inflate64==1.0.0 28 | # via py7zr 29 | multivolumefile==0.2.3 30 | # via py7zr 31 | packaging==24.1 32 | # via 33 | # -r requirements.in 34 | # customtkinter 35 | pillow==10.4.0 36 | # via 37 | # -r requirements.in 38 | # ctkmessagebox 39 | platformdirs==4.3.6 40 | # via -r requirements.in 41 | psutil==6.0.0 42 | # via py7zr 43 | py7zr==0.22.0 44 | # via -r requirements.in 45 | pybcj==1.0.2 46 | # via py7zr 47 | pycryptodomex==3.20.0 48 | # via py7zr 49 | pyppmd==1.1.0 50 | # via py7zr 51 | pyzstd==0.16.1 52 | # via py7zr 53 | requests==2.32.3 54 | # via -r requirements.in 55 | soupsieve==2.6 56 | # via beautifulsoup4 57 | texttable==1.7.0 58 | # via py7zr 59 | urllib3==2.2.2 60 | # via requests 61 | -------------------------------------------------------------------------------- /src/assets/fonts/haxrcorp-4089/haxrcorp-4089.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/fonts/haxrcorp-4089/haxrcorp-4089.ttf -------------------------------------------------------------------------------- /src/assets/fonts/haxrcorp-4089/license.txt: -------------------------------------------------------------------------------- 1 | The FontStruction “HaxrCorp 4089” 2 | (https://fontstruct.com/fontstructions/show/192981) by “sahwar” is licensed 3 | under a Creative Commons Attribution Share Alike license 4 | (http://creativecommons.org/licenses/by-sa/3.0/). 5 | -------------------------------------------------------------------------------- /src/assets/fonts/haxrcorp-4089/readme.txt: -------------------------------------------------------------------------------- 1 | The font file in this archive was created using Fontstruct the free, online 2 | font-building tool. 3 | This font was created by “sahwar”. 4 | This font has a homepage where this archive and other versions may be found: 5 | https://fontstruct.com/fontstructions/show/192981 6 | 7 | Try Fontstruct at https://fontstruct.com 8 | It’s easy and it’s fun. 9 | 10 | Fontstruct is copyright ©2009-2023 Rob Meek 11 | 12 | LEGAL NOTICE: 13 | In using this font you must comply with the licensing terms described in the 14 | file “license.txt” included with this archive. 15 | If you redistribute the font file in this archive, it must be accompanied by all 16 | the other files from this archive, including this one. 17 | -------------------------------------------------------------------------------- /src/assets/images/discord_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/discord_icon.png -------------------------------------------------------------------------------- /src/assets/images/dolphin_banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/dolphin_banner_dark.png -------------------------------------------------------------------------------- /src/assets/images/dolphin_banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/dolphin_banner_light.png -------------------------------------------------------------------------------- /src/assets/images/dolphin_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/dolphin_logo.png -------------------------------------------------------------------------------- /src/assets/images/emuhaven_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/emuhaven_logo.ico -------------------------------------------------------------------------------- /src/assets/images/emuhaven_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/emuhaven_logo.png -------------------------------------------------------------------------------- /src/assets/images/github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/github-mark-white.png -------------------------------------------------------------------------------- /src/assets/images/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/github-mark.png -------------------------------------------------------------------------------- /src/assets/images/kofi_button_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/kofi_button_blue.png -------------------------------------------------------------------------------- /src/assets/images/kofi_button_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/kofi_button_red.png -------------------------------------------------------------------------------- /src/assets/images/kofi_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/kofi_icon.png -------------------------------------------------------------------------------- /src/assets/images/placeholder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/placeholder_icon.png -------------------------------------------------------------------------------- /src/assets/images/play_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/play_dark.png -------------------------------------------------------------------------------- /src/assets/images/play_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/play_light.png -------------------------------------------------------------------------------- /src/assets/images/ryujinx_banner-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/ryujinx_banner-original.png -------------------------------------------------------------------------------- /src/assets/images/ryujinx_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/ryujinx_banner.png -------------------------------------------------------------------------------- /src/assets/images/ryujinx_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/ryujinx_logo.png -------------------------------------------------------------------------------- /src/assets/images/settings_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/settings_dark.png -------------------------------------------------------------------------------- /src/assets/images/settings_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/settings_light.png -------------------------------------------------------------------------------- /src/assets/images/xenia_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/xenia_banner.png -------------------------------------------------------------------------------- /src/assets/images/xenia_canary_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/xenia_canary_banner.png -------------------------------------------------------------------------------- /src/assets/images/xenia_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/xenia_logo.png -------------------------------------------------------------------------------- /src/assets/images/yuzu_early_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/yuzu_early_access.png -------------------------------------------------------------------------------- /src/assets/images/yuzu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/yuzu_logo.png -------------------------------------------------------------------------------- /src/assets/images/yuzu_mainline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/src/assets/images/yuzu_mainline.png -------------------------------------------------------------------------------- /src/assets/themes/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rigved Maanas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assets/themes/autumn.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#F5E5E1", "#3B2F2A"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#F5E5E1", "#3B2F2A"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#E1C9B7", "#3F2F25"], 12 | "top_fg_color": ["#D5B79B", "#4D3B2A"], 13 | "border_color": ["#A6806F", "#6D3F28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#BF6F5F", "#9B4A3C"], 19 | "hover_color": ["#C8766D", "#A03E29"], 20 | "border_color": ["#BF8F7C", "#6D4F4D"], 21 | "text_color": ["#F0E8E2", "#F0E8E2"], 22 | "text_color_disabled": ["#B08976", "#8D6E6E"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#3B2F2A", "#F0E8E2"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F8F4F1", "#3A3C37"], 33 | "border_color": ["#9C8A7F", "#5E5B55"], 34 | "text_color": ["#3B2F2A", "#F0E8E2"], 35 | "placeholder_text_color": ["#9C8A7F", "#8A7D6C"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#BF6F5F", "#9B4A3C"], 41 | "border_color": ["#BF8F7C", "#6D4F4D"], 42 | "hover_color": ["#BF6F5F", "#9B4A3C"], 43 | "checkmark_color": ["#F0E8E2", "#D5B9A1"], 44 | "text_color": ["#3B2F2A", "#F0E8E2"], 45 | "text_color_disabled": ["#8D6E6E", "#6B4F4F"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#9C8A7F", "#4F4D48"], 52 | "progress_color": ["#BF6F5F", "#9B4A3C"], 53 | "button_color": ["#5F4A4B", "#D4C2B5"], 54 | "button_hover_color": ["#3F2F2A", "#F0E8E2"], 55 | "text_color": ["#3B2F2A", "#F0E8E2"], 56 | "text_color_disabled": ["#8D6E6E", "#6B4F4F"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#BF6F5F", "#9B4A3C"], 63 | "border_color": ["#BF8F7C", "#6D4F4D"], 64 | "hover_color": ["#C8766D", "#A03E29"], 65 | "text_color": ["#3B2F2A", "#F0E8E2"], 66 | "text_color_disabled": ["#8D6E6E", "#6B4F4F"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#9C8A7F", "#4F4D48"], 72 | "progress_color": ["#BF6F5F", "#9B4A3C"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#9C8A7F", "#4F4D48"], 81 | "progress_color": ["#6D4D4F", "#BFA6A5"], 82 | "button_color": ["#BF6F5F", "#9B4A3C"], 83 | "button_hover_color": ["#C8766D", "#A03E29"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#BF6F5F", "#9B4A3C"], 88 | "button_color": ["#C8766D", "#A03E29"], 89 | "button_hover_color": ["#8A5A4E", "#5F3F2E"], 90 | "text_color": ["#F0E8E2", "#F0E8E2"], 91 | "text_color_disabled": ["#B08976", "#8D6E6E"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F8F4F1", "#3A3C37"], 97 | "border_color": ["#9C8A7F", "#5E5B55"], 98 | "button_color": ["#9C8A7F", "#5E5B55"], 99 | "button_hover_color": ["#7C6F6E", "#8A7F80"], 100 | "text_color": ["#3B2F2A", "#F0E8E2"], 101 | "text_color_disabled": ["#8A7D6C", "#6B4F4F"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#8D6E6E", "#6B4F4F"], 108 | "button_hover_color": ["#6D4F4F", "#7F6E6E"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#9C8A7F", "#6D4F4D"], 114 | "selected_color": ["#BF6F5F", "#9B4A3C"], 115 | "selected_hover_color": ["#C8766D", "#A03E29"], 116 | "unselected_color": ["#9C8A7F", "#6D4F4D"], 117 | "unselected_hover_color": ["#8D6E6E", "#6B4F4F"], 118 | "text_color": ["#F0E8E2", "#F0E8E2"], 119 | "text_color_disabled": ["#B08976", "#8D6E6E"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F8F4F1", "#3D3D3D"], 125 | "border_color": ["#9C8A7F", "#5E5B55"], 126 | "text_color": ["#3B2F2A", "#F0E8E2"], 127 | "scrollbar_button_color": ["#8D6E6E", "#6B4F4F"], 128 | "scrollbar_button_hover_color": ["#6D4F4F", "#7F6E6E"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#D0B4A8", "#4D3B2A"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#F0E8E2", "#4D3B2A"], 135 | "hover_color": ["#D5B9A1", "#6D3F28"], 136 | "text_color": ["#3B2F2A", "#F0E8E2"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/breeze.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#B4D9E7", "#1E3A46"], 12 | "top_fg_color": ["#A0C8D6", "#263F4F"], 13 | "border_color": ["#8AA1AE", "#2C4B5A"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#4DB3C8", "#2D6D7D"], 19 | "hover_color": ["#4A9AB3", "#2B5B6D"], 20 | "border_color": ["#4D7A8D", "#9B9F9F"], 21 | "text_color": ["#E1F2F6", "#E1F2F6"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#E1F2F6"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F2F9FA", "#2A3A3E"], 33 | "border_color": ["#9AAEB1", "#546063"], 34 | "text_color":["gray10", "#E1F2F6"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#4DB3C8", "#2D6D7D"], 41 | "border_color": ["#4D7A8D", "#9B9F9F"], 42 | "hover_color": ["#4DB3C8", "#2D6D7D"], 43 | "checkmark_color": ["#E1F2F6", "gray90"], 44 | "text_color": ["gray10", "#E1F2F6"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#8E9B9E", "#4F5A5D"], 52 | "progress_color": ["#4DB3C8", "#2D6D7D"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#E1F2F6"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#4DB3C8", "#2D6D7D"], 63 | "border_color": ["#4D7A8D", "#9B9F9F"], 64 | "hover_color": ["#4A9AB3", "#2B5B6D"], 65 | "text_color": ["gray10", "#E1F2F6"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#8E9B9E", "#4F5A5D"], 72 | "progress_color": ["#4DB3C8", "#2D6D7D"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#8E9B9E", "#4F5A5D"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#4DB3C8", "#2D6D7D"], 83 | "button_hover_color": ["#4A9AB3", "#2B5B6D"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#4DB3C8", "#2D6D7D"], 88 | "button_color": ["#4A9AB3", "#2B5B6D"], 89 | "button_hover_color": ["#307C8A", "#1F4C5C"], 90 | "text_color": ["#E1F2F6", "#E1F2F6"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F2F9FA", "#2A3A3E"], 97 | "border_color": ["#9AAEB1", "#546063"], 98 | "button_color": ["#9AAEB1", "#546063"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#E1F2F6"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#9AAEB1", "gray29"], 114 | "selected_color": ["#4DB3C8", "#2D6D7D"], 115 | "selected_hover_color": ["#4A9AB3", "#2B5B6D"], 116 | "unselected_color": ["#9AAEB1", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#E1F2F6", "#E1F2F6"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F2F9FA", "#1D1E1E"], 125 | "border_color": ["#9AAEB1", "#546063"], 126 | "text_color":["gray10", "#E1F2F6"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/cherry.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#FBE8E6", "#6D4C6C"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#FBE8E6", "#6D4C6C"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#FFF1EA", "#5D3A4A"], 12 | "top_fg_color": ["#F4D8D0", "#5A2F3E"], 13 | "border_color": ["#C6A6A5", "#6F3D4B"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#F5B3B3", "#C85C5C"], 19 | "hover_color": ["#F29C9C", "#BF3E3E"], 20 | "border_color": ["#F4A3A0", "#A8A8A8"], 21 | "text_color": ["#FDF5F5", "#FDF5F5"], 22 | "text_color_disabled": ["#D8A7A2", "#B68D8D"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#6D4C6C", "#FDF5F5"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#FDF5F5", "#3C2A2F"], 33 | "border_color": ["#D8A7A2", "#6F3D4B"], 34 | "text_color": ["#6D4C6C", "#FDF5F5"], 35 | "placeholder_text_color": ["#C5A2A2", "#B68D8D"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#F5B3B3", "#C85C5C"], 41 | "border_color": ["#F4A3A0", "#A8A8A8"], 42 | "hover_color": ["#F5B3B3", "#C85C5C"], 43 | "checkmark_color": ["#FDF5F5", "#D8A7A2"], 44 | "text_color": ["#6D4C6C", "#FDF5F5"], 45 | "text_color_disabled": ["#B68D8D", "#A67D7D"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#D8A7A2", "#6F3D4B"], 52 | "progress_color": ["#F5B3B3", "#C85C5C"], 53 | "button_color": ["#6D4C6C", "#F6E7E6"], 54 | "button_hover_color": ["#5C3B3B", "#F9F9F9"], 55 | "text_color": ["#6D4C6C", "#FDF5F5"], 56 | "text_color_disabled": ["#B68D8D", "#A67D7D"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#F5B3B3", "#C85C5C"], 63 | "border_color": ["#F4A3A0", "#A8A8A8"], 64 | "hover_color": ["#F29C9C", "#BF3E3E"], 65 | "text_color": ["#6D4C6C", "#FDF5F5"], 66 | "text_color_disabled": ["#B68D8D", "#A67D7D"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#D8A7A2", "#6F3D4B"], 72 | "progress_color": ["#F5B3B3", "#C85C5C"], 73 | "border_color": ["#B68D8D", "#B68D8D"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#D8A7A2", "#6F3D4B"], 81 | "progress_color": ["#C5A2A2", "#B68D8D"], 82 | "button_color": ["#F5B3B3", "#C85C5C"], 83 | "button_hover_color": ["#F29C9C", "#BF3E3E"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#F5B3B3", "#C85C5C"], 88 | "button_color": ["#F29C9C", "#BF3E3E"], 89 | "button_hover_color": ["#E57C7C", "#B64D4D"], 90 | "text_color": ["#FDF5F5", "#FDF5F5"], 91 | "text_color_disabled": ["#D8A7A2", "#B68D8D"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#FDF5F5", "#3C2A2F"], 97 | "border_color": ["#D8A7A2", "#6F3D4B"], 98 | "button_color": ["#D8A7A2", "#6F3D4B"], 99 | "button_hover_color": ["#C5A2A2", "#B68D8D"], 100 | "text_color": ["#6D4C6C", "#FDF5F5"], 101 | "text_color_disabled": ["#C5A2A2", "#A67D7D"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#D8A7A2", "#6F3D4B"], 108 | "button_hover_color": ["#C5A2A2", "#B68D8D"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#D8A7A2", "#6F3D4B"], 114 | "selected_color": ["#F5B3B3", "#C85C5C"], 115 | "selected_hover_color": ["#F29C9C", "#BF3E3E"], 116 | "unselected_color": ["#D8A7A2", "#6F3D4B"], 117 | "unselected_hover_color": ["#C5A2A2", "#B68D8D"], 118 | "text_color": ["#FDF5F5", "#FDF5F5"], 119 | "text_color_disabled": ["#D8A7A2", "#B68D8D"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#FDF5F5", "#3C2A2F"], 125 | "border_color": ["#D8A7A2", "#6F3D4B"], 126 | "text_color": ["#6D4C6C", "#FDF5F5"], 127 | "scrollbar_button_color": ["#D8A7A2", "#6F3D4B"], 128 | "scrollbar_button_hover_color": ["#C5A2A2", "#B68D8D"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#F1D3D1", "#6F3D4B"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#FBE8E6", "#6F3D4B"], 135 | "hover_color": ["#F4D8D0", "#5A2F3E"], 136 | "text_color": ["#6D4C6C", "#FBE8E6"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/coffee.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#E8D1B3", "#6D5143"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#E8D1B3", "#6D5143"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#D8C0A0", "#725B4E"], 12 | "top_fg_color": ["#C2A685", "#7F6358"], 13 | "border_color": ["#A3886F", "#8B746A"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#825C46", "#5A3E32"], 19 | "hover_color": ["#6F4C37", "#4B3126"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["#A89B89", "#8E7D72"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#6D5143", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#EDE0CF", "#1D1E1E"], 33 | "border_color": ["#AFA292", "#725B4E"], 34 | "text_color": ["#6D5143", "#DCE4EE"], 35 | "placeholder_text_color": ["#9F8B76", "#A89B89"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#825C46", "#5A3E32"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#825C46", "#5A3E32"], 43 | "checkmark_color": ["#DCE4EE", "#B8A094"], 44 | "text_color": ["#6D5143", "#DCE4EE"], 45 | "text_color_disabled": ["#A89B89", "#8E7D72"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#9F9488", "#736A60"], 52 | "progress_color": ["#825C46", "#5A3E32"], 53 | "button_color": ["#725B4E", "#D5D9DE"], 54 | "button_hover_color": ["#6D5143", "#DCE4EE"], 55 | "text_color": ["#6D5143", "#DCE4EE"], 56 | "text_color_disabled": ["#A89B89", "#8E7D72"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#825C46", "#5A3E32"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#6F4C37", "#4B3126"], 65 | "text_color": ["#6D5143", "#DCE4EE"], 66 | "text_color_disabled": ["#A89B89", "#8E7D72"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#9F9488", "#736A60"], 72 | "progress_color": ["#825C46", "#5A3E32"], 73 | "border_color": ["#6D5143", "#6D5143"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#9F9488", "#736A60"], 81 | "progress_color": ["#8E745E", "#7F6D64"], 82 | "button_color": ["#825C46", "#5A3E32"], 83 | "button_hover_color": ["#6F4C37", "#4B3126"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#825C46", "#5A3E32"], 88 | "button_color": ["#6F4C37", "#4B3126"], 89 | "button_hover_color": ["#593B29", "#3C271A"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["#A89B89", "#8E7D72"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#EDE0CF", "#6D5143"], 97 | "border_color": ["#AFA292", "#725B4E"], 98 | "button_color": ["#AFA292", "#725B4E"], 99 | "button_hover_color": ["#7E6E5F", "#7A6153"], 100 | "text_color": ["#6D5143", "#DCE4EE"], 101 | "text_color_disabled": ["#AA9884", "#8E7D72"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#8B746A", "#725B4E"], 108 | "button_hover_color": ["#6F4C37", "#4B3126"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#AFA292", "#6D5143"], 114 | "selected_color": ["#825C46", "#5A3E32"], 115 | "selected_hover_color": ["#6F4C37", "#4B3126"], 116 | "unselected_color": ["#AFA292", "#6D5143"], 117 | "unselected_hover_color": ["#8E745E", "#7F6D64"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["#A89B89", "#8E7D72"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#EDE0CF", "#1D1E1E"], 125 | "border_color": ["#AFA292", "#725B4E"], 126 | "text_color": ["#6D5143", "#DCE4EE"], 127 | "scrollbar_button_color": ["#8B746A", "#725B4E"], 128 | "scrollbar_button_hover_color": ["#6F4C37", "#4B3126"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#B8A094", "#7F6358"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#B8A094", "#725B4E"], 135 | "hover_color": ["#A89B89", "#8E7D72"], 136 | "text_color": ["#6D5143", "#B8A094"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/flipper-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#000000", "#000000"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#000000", "#000000"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 2, 11 | "fg_color": ["#0b0000", "#0b0000"], 12 | "top_fg_color": ["#000000", "#000000"], 13 | "border_color": ["#fe9433", "#fe9433"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 2, 18 | "fg_color": ["#532000", "#532000"], 19 | "hover_color": ["#814007", "#814007"], 20 | "border_color": ["#fe9433", "#fe9433"], 21 | "text_color": ["#fe9433", "#fe9433"], 22 | "text_color_disabled": ["#98591f", "#98591f"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#fe9433", "#fe9433"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#000000", "#000000"], 33 | "border_color": ["#fe9433", "#fe9433"], 34 | "text_color":["#fe9433", "#fe9433"], 35 | "placeholder_text_color": ["#98591f", "#98591f"] 36 | }, 37 | "CTkCheckbox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#e5852e", "#e5852e"], 41 | "border_color": ["#532000", "#532000"], 42 | "hover_color": ["#e5852e", "#e5852e"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["#fe9433", "#fe9433"], 45 | "text_color_disabled": ["#98591f", "#98591f"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#663b14", "#663b14"], 52 | "progress_color": ["#6d4934", "#6d4934"], 53 | "button_color": ["#fe9433", "#fe9433"], 54 | "button_hover_color": ["#fea95c", "#fea95c"], 55 | "text_color": ["#fe9433", "#fe9433"], 56 | "text_color_disabled": ["#98591f", "#98591f"] 57 | }, 58 | "CTkRadiobutton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#e5852e", "#e5852e"], 63 | "border_color": ["#532000", "#532000"], 64 | "hover_color": ["#e5852e", "#e5852e"], 65 | "text_color": ["#fe9433", "#fe9433"], 66 | "text_color_disabled": ["#98591f", "#98591f"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#532000", "#532000"], 72 | "progress_color": ["#e5852e", "#e5852e"], 73 | "border_color": ["#0b0000", "#0b0000"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#532000", "#532000"], 81 | "progress_color": ["#6d4934", "#6d4934"], 82 | "button_color": ["#fe9433", "#fe9433"], 83 | "button_hover_color": ["#fea95c", "#fea95c"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#532000", "#532000"], 88 | "button_color": ["#3a1600", "#3a1600"], 89 | "button_hover_color": ["#754d33", "#754d33"], 90 | "text_color": ["#e5852e", "#e5852e"], 91 | "text_color_disabled": ["#cb7629", "#cb7629"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#532000", "#532000"], 97 | "border_color": ["#e5852e", "#e5852e"], 98 | "button_color": ["#532000", "#532000"], 99 | "button_hover_color": ["#754d33", "#754d33"], 100 | "text_color": ["#e5852e", "#e5852e"], 101 | "text_color_disabled": ["#cb7629", "#cb7629"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#Fe9433", "#Fe9433"], 108 | "button_hover_color": ["#cb7629", "#cb7629"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#2a1000", "#2a1000"], 114 | "selected_color": ["#814007", "#814007"], 115 | "selected_hover_color": ["#7f4a1a", "#7f4a1a"], 116 | "unselected_color": ["#2a1000", "#2a1000"], 117 | "unselected_hover_color": ["#3a1600", "#3a1600"], 118 | "text_color": ["#Fe9433", "#Fe9433"], 119 | "text_color_disabled": ["#cb7629", "#cb7629"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 2, 124 | "fg_color": ["#532000", "#532000"], 125 | "border_color": ["#Fe9433", "#Fe9433"], 126 | "text_color": ["#Fe9433", "#Fe9433"], 127 | "scrollbar_button_color": ["#Fe9433", "#Fe9433"], 128 | "scrollbar_button_hover_color": ["#cb7629", "#cb7629"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#532000", "#532000"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "HaxrCorp4089", 141 | "size": 25, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "HaxrCorp4089", 146 | "size": 25, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "HaxrCorp4089", 151 | "size": 25, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/lavender.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["gray86", "gray17"], 12 | "top_fg_color": ["gray81", "gray20"], 13 | "border_color": ["gray65", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#B19CD9", "#9370DB"], 19 | "hover_color": ["#9370DB", "#7A5DC7"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F9F9FA", "#343638"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color": ["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#B19CD9", "#9370DB"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#B19CD9", "#9370DB"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#939BA2", "#4A4D50"], 52 | "progress_color": ["#B19CD9", "#9370DB"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#B19CD9", "#9370DB"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#9370DB", "#7A5DC7"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#939BA2", "#4A4D50"], 72 | "progress_color": ["#B19CD9", "#9370DB"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#939BA2", "#4A4D50"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#B19CD9", "#9370DB"], 83 | "button_hover_color": ["#9370DB", "#7A5DC7"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#B19CD9", "#9370DB"], 88 | "button_color": ["#9370DB", "#7A5DC7"], 89 | "button_hover_color": ["#8368B5", "#695399"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F9F9FA", "#343638"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#979DA2", "#565B5E"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#979DA2", "gray29"], 114 | "selected_color": ["#B19CD9", "#9370DB"], 115 | "selected_hover_color": ["#9370DB", "#7A5DC7"], 116 | "unselected_color": ["#979DA2", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F9F9FA", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color": ["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/marsh.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#B0C9A0", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#B0C9A0", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#A3BE91", "gray17"], 12 | "top_fg_color": ["#9AB77C", "gray20"], 13 | "border_color": ["#8A9E72", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#7DBE98", "#4E8F69"], 19 | "hover_color": ["#6EAA8C", "#397F5A"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#F0F8ED", "#F0F8ED"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#F0F8ED"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#C9E6D5", "#5E7B6B"], 33 | "border_color": ["#7E977D", "#465D54"], 34 | "text_color":["gray10", "#F0F8ED"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#7DBE98", "#4E8F69"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#7DBE98", "#4E8F69"], 43 | "checkmark_color": ["#F0F8ED", "gray90"], 44 | "text_color": ["gray10", "#F0F8ED"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#AEBEAD", "#6E7E6E"], 52 | "progress_color": ["#7DBE98", "#4E8F69"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#F0F8ED"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#7DBE98", "#4E8F69"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#6EAA8C", "#397F5A"], 65 | "text_color": ["gray10", "#F0F8ED"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#AEBEAD", "#6E7E6E"], 72 | "progress_color": ["#7DBE98", "#4E8F69"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#AEBEAD", "#6E7E6E"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#7DBE98", "#4E8F69"], 83 | "button_hover_color": ["#6EAA8C", "#397F5A"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#7DBE98", "#4E8F69"], 88 | "button_color": ["#6EAA8C", "#397F5A"], 89 | "button_hover_color": ["#578369", "#315543"], 90 | "text_color": ["#F0F8ED", "#F0F8ED"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#C9E6D5", "#5E7B6B"], 97 | "border_color": ["#7E977D", "#465D54"], 98 | "button_color": ["#7E977D", "#465D54"], 99 | "button_hover_color": ["#95A290", "#678070"], 100 | "text_color": ["gray10", "#F0F8ED"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#7E977D", "gray29"], 114 | "selected_color": ["#7DBE98", "#4E8F69"], 115 | "selected_hover_color": ["#6EAA8C", "#397F5A"], 116 | "unselected_color": ["#7E977D", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#F0F8ED", "#F0F8ED"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#C9E6D5", "#1D1E1E"], 125 | "border_color": ["#7E977D", "#465D54"], 126 | "text_color":["gray10", "#F0F8ED"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#B0C9A0", "gray20"], 135 | "hover_color": ["#A1BB8D", "gray28"], 136 | "text_color": ["gray10", "#B0C9A0"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/metal.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#E0E0E0", "#363636"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#E0E0E0", "#363636"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#C0C0C0", "#404040"], 12 | "top_fg_color": ["#A0A0A0", "#505050"], 13 | "border_color": ["#808080", "#606060"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#A0A0A0", "#505050"], 19 | "hover_color": ["#909090", "#606060"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#F0F0F0", "#F0F0F0"], 22 | "text_color_disabled": ["#C0C0C0", "#808080"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#363636", "#F0F0F0"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F0F0F0", "#363636"], 33 | "border_color": ["#808080", "#606060"], 34 | "text_color": ["#363636", "#F0F0F0"], 35 | "placeholder_text_color": ["#878787", "#757575"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#A0A0A0", "#505050"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#A0A0A0", "#505050"], 43 | "checkmark_color": ["#F0F0F0", "#D0D0D0"], 44 | "text_color": ["#363636", "#F0F0F0"], 45 | "text_color_disabled": ["#808080", "#6C6C6C"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#B0B0B0", "#505050"], 52 | "progress_color": ["#A0A0A0", "#505050"], 53 | "button_color": ["#606060", "#D0D0D0"], 54 | "button_hover_color": ["#404040", "#F0F0F0"], 55 | "text_color": ["#363636", "#F0F0F0"], 56 | "text_color_disabled": ["#808080", "#6C6C6C"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#A0A0A0", "#505050"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#909090", "#606060"], 65 | "text_color": ["#363636", "#F0F0F0"], 66 | "text_color_disabled": ["#808080", "#6C6C6C"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#B0B0B0", "#505050"], 72 | "progress_color": ["#A0A0A0", "#505050"], 73 | "border_color": ["#808080", "#808080"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#B0B0B0", "#505050"], 81 | "progress_color": ["#707070", "#808080"], 82 | "button_color": ["#A0A0A0", "#505050"], 83 | "button_hover_color": ["#909090", "#606060"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#A0A0A0", "#505050"], 88 | "button_color": ["#909090", "#606060"], 89 | "button_hover_color": ["#707070", "#404040"], 90 | "text_color": ["#F0F0F0", "#F0F0F0"], 91 | "text_color_disabled": ["#C0C0C0", "#808080"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F0F0F0", "#363636"], 97 | "border_color": ["#808080", "#606060"], 98 | "button_color": ["#808080", "#606060"], 99 | "button_hover_color": ["#707070", "#404040"], 100 | "text_color": ["#363636", "#F0F0F0"], 101 | "text_color_disabled": ["#A0A0A0", "#808080"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#606060", "#505050"], 108 | "button_hover_color": ["#404040", "#606060"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#808080", "#363636"], 114 | "selected_color": ["#A0A0A0", "#505050"], 115 | "selected_hover_color": ["#909090", "#606060"], 116 | "unselected_color": ["#808080", "#363636"], 117 | "unselected_hover_color": ["#B0B0B0", "#505050"], 118 | "text_color": ["#F0F0F0", "#F0F0F0"], 119 | "text_color_disabled": ["#C0C0C0", "#808080"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F0F0F0", "#1D1E1E"], 125 | "border_color": ["#808080", "#606060"], 126 | "text_color": ["#363636", "#F0F0F0"], 127 | "scrollbar_button_color": ["#606060", "#505050"], 128 | "scrollbar_button_hover_color": ["#404040", "#606060"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#A0A0A0", "#505050"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#E0E0E0", "#363636"], 135 | "hover_color": ["#C0C0C0", "#404040"], 136 | "text_color": ["#363636", "#E0E0E0"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/midnight.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#DCE4EE", "#001F3F"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#DCE4EE", "#001F3F"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#BCC6D0", "#434E5A"], 12 | "top_fg_color": ["#AAB0B6", "#003366"], 13 | "border_color": ["#5A5C66", "#003B6C"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#003F6C", "#002E5D"], 19 | "hover_color": ["#004080", "#00214B"], 20 | "border_color": ["#003D4E", "#7F8C8D"], 21 | "text_color": ["#E5E9F0", "#E5E9F0"], 22 | "text_color_disabled": ["#C0C6CE", "#A6A9AE"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#001F3F", "#E5E9F0"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F2F4F7", "#002B5B"], 33 | "border_color": ["#7D8B92", "#004060"], 34 | "text_color": ["#001F3F", "#E5E9F0"], 35 | "placeholder_text_color": ["#7D8B92", "#8B8F92"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#003F6C", "#002E5D"], 41 | "border_color": ["#003D4E", "#7F8C8D"], 42 | "hover_color": ["#003F6C", "#002E5D"], 43 | "checkmark_color": ["#E5E9F0", "#BCC6D0"], 44 | "text_color": ["#001F3F", "#E5E9F0"], 45 | "text_color_disabled": ["#A6A9AE", "#8C8D8F"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#7D8B92", "#003B6C"], 52 | "progress_color": ["#003F6C", "#002E5D"], 53 | "button_color": ["#002B5B", "#E5E9F0"], 54 | "button_hover_color": ["#003366", "#E5E9F0"], 55 | "text_color": ["#001F3F", "#E5E9F0"], 56 | "text_color_disabled": ["#A6A9AE", "#8C8D8F"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#003F6C", "#002E5D"], 63 | "border_color": ["#003D4E", "#7F8C8D"], 64 | "hover_color": ["#004080", "#00214B"], 65 | "text_color": ["#001F3F", "#E5E9F0"], 66 | "text_color_disabled": ["#A6A9AE", "#8C8D8F"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#7D8B92", "#003B6C"], 72 | "progress_color": ["#003F6C", "#002E5D"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#7D8B92", "#003B6C"], 81 | "progress_color": ["#003D4E", "#B0B3B6"], 82 | "button_color": ["#003F6C", "#002E5D"], 83 | "button_hover_color": ["#004080", "#00214B"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#003F6C", "#002E5D"], 88 | "button_color": ["#004080", "#00214B"], 89 | "button_hover_color": ["#002E5D", "#001F3F"], 90 | "text_color": ["#E5E9F0", "#E5E9F0"], 91 | "text_color_disabled": ["#C0C6CE", "#A6A9AE"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F2F4F7", "#002B5B"], 97 | "border_color": ["#7D8B92", "#004060"], 98 | "button_color": ["#7D8B92", "#004060"], 99 | "button_hover_color": ["#4F5B66", "#5B6C77"], 100 | "text_color": ["#001F3F", "#E5E9F0"], 101 | "text_color_disabled": ["#8C8D8F", "#8C8D8F"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#8C8D8F", "#6D6E70"], 108 | "button_hover_color": ["#7D8B92", "#5A5B5C"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#7D8B92", "#003B6C"], 114 | "selected_color": ["#003F6C", "#002E5D"], 115 | "selected_hover_color": ["#004080", "#00214B"], 116 | "unselected_color": ["#7D8B92", "#003B6C"], 117 | "unselected_hover_color": ["#7F8C8D", "#5A5B5C"], 118 | "text_color": ["#E5E9F0", "#E5E9F0"], 119 | "text_color_disabled": ["#C0C6CE", "#A6A9AE"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F2F4F7", "#002B5B"], 125 | "border_color": ["#7D8B92", "#004060"], 126 | "text_color": ["#001F3F", "#E5E9F0"], 127 | "scrollbar_button_color": ["#8C8D8F", "#6D6E70"], 128 | "scrollbar_button_hover_color": ["#7D8B92", "#5A5B5C"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#BCC6D0", "#003B6C"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#DCE4EE", "#003B6C"], 135 | "hover_color": ["#BCC6D0", "#003B6C"], 136 | "text_color": ["#001F3F", "#DCE4EE"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/orange.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["gray86", "gray17"], 12 | "top_fg_color": ["gray81", "gray20"], 13 | "border_color": ["gray65", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#FF8C42", "#FF6505"], 19 | "hover_color": ["#E67320", "#CC5500"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F9F9FA", "#343638"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color":["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#FF8C42", "#FF6505"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#FF8C42", "#FF6505"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#939BA2", "#4A4D50"], 52 | "progress_color": ["#FF8C42", "#FF6505"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#FF8C42", "#FF6505"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#E67320", "#CC5500"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#939BA2", "#4A4D50"], 72 | "progress_color": ["#FF8C42", "#FF6505"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#939BA2", "#4A4D50"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#FF8C42", "#FF6505"], 83 | "button_hover_color": ["#E67320", "#CC5500"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#FF8C42", "#FF6505"], 88 | "button_color": ["#E67320", "#CC5500"], 89 | "button_hover_color": ["#CC5500", "#994411"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F9F9FA", "#343638"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#979DA2", "#565B5E"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#979DA2", "gray29"], 114 | "selected_color": ["#FF8C42", "#FF6505"], 115 | "selected_hover_color": ["#E67320", "#CC5500"], 116 | "unselected_color": ["#979DA2", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F9F9FA", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color":["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/patina.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#DFB392", "#CC9E7A"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#DAB396", "#D1A482"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#CAAFA4", "#AC968D"], 12 | "top_fg_color": ["#B89B75", "#B39268"], 13 | "border_color": ["#A67C5F", "#926E54"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#B57A4C", "#6F4C3B"], 19 | "hover_color": ["#A9643A", "#5C3A24"], 20 | "border_color": ["#9B5D3A", "#3E2D1F"], 21 | "text_color": ["#F5F2E6", "#F5EDE6"], 22 | "text_color_disabled": ["#C4A97B", "#9B8C6D"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#5C3F2D", "#F5EDE6"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F5F2E6", "#3E2D1F"], 33 | "border_color": ["#9B7D6F", "#6E5B4E"], 34 | "text_color": ["#5C3F2D", "#F5EDE6"], 35 | "placeholder_text_color": ["#9F8B5F", "#9B8C6D"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#B57A4C", "#6F4C3B"], 41 | "border_color": ["#9B5D3A", "#3E2D1F"], 42 | "hover_color": ["#B57A4C", "#6F4C3B"], 43 | "checkmark_color": ["#F5F2E6", "#D0B8A4"], 44 | "text_color": ["#5C3F2D", "#F5EDE6"], 45 | "text_color_disabled": ["#9F8B5F", "#8F7C6D"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#9B7D6F", "#6E5B4E"], 52 | "progress_color": ["#B57A4C", "#6F4C3B"], 53 | "button_color": ["#6F4F28", "#D1C6B0"], 54 | "button_hover_color": ["#4A3F32", "#F5EDE6"], 55 | "text_color": ["#5C3F2D", "#F5EDE6"], 56 | "text_color_disabled": ["#9F8B5F", "#8F7C6D"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#B57A4C", "#6F4C3B"], 63 | "border_color": ["#9B5D3A", "#3E2D1F"], 64 | "hover_color": ["#A9643A", "#5C3A24"], 65 | "text_color": ["#5C3F2D", "#F5EDE6"], 66 | "text_color_disabled": ["#9F8B5F", "#8F7C6D"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#9B7D6F", "#6E5B4E"], 72 | "progress_color": ["#B57A4C", "#6F4C3B"], 73 | "border_color": ["#B1A38F", "#6E5B4E"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#9B7D6F", "#6E5B4E"], 81 | "progress_color": ["gray40", "#C0B1A1"], 82 | "button_color": ["#B57A4C", "#6F4C3B"], 83 | "button_hover_color": ["#A9643A", "#5C3A24"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#B57A4C", "#6F4C3B"], 88 | "button_color": ["#A9643A", "#5C3A24"], 89 | "button_hover_color": ["#8A4B2D", "#4F3A24"], 90 | "text_color": ["#F5F2E6", "#F5EDE6"], 91 | "text_color_disabled": ["#C4A97B", "#9B8C6D"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F5F2E6", "#3E2D1F"], 97 | "border_color": ["#9B7D6F", "#6E5B4E"], 98 | "button_color": ["#9B7D6F", "#6E5B4E"], 99 | "button_hover_color": ["#8A4B2D", "#9B8C6D"], 100 | "text_color": ["#5C3F2D", "#F5EDE6"], 101 | "text_color_disabled": ["#9F8B5F", "#7A5F4F"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#9F8B5F", "#7A5F4F"], 108 | "button_hover_color": ["#8A4B2D", "#9B8C6D"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#9B7D6F", "#4F3A24"], 114 | "selected_color": ["#B57A4C", "#6F4C3B"], 115 | "selected_hover_color": ["#A9643A", "#5C3A24"], 116 | "unselected_color": ["#9B7D6F", "#4F3A24"], 117 | "unselected_hover_color": ["#C4A97B", "#7A5F4F"], 118 | "text_color": ["#F5F2E6", "#F5EDE6"], 119 | "text_color_disabled": ["#C4A97B", "#9B8C6D"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F5F2E6", "#3E2D1F"], 125 | "border_color": ["#9B7D6F", "#6E5B4E"], 126 | "text_color": ["#5C3F2D", "#F5EDE6"], 127 | "scrollbar_button_color": ["#9F8B5F", "#7A5F4F"], 128 | "scrollbar_button_hover_color": ["#8A4B2D", "#9B8C6D"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#C4A97B", "#6A4F41"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#D4A37E", "#6A4F41"], 135 | "hover_color": ["#B89B75", "#6E5B4E"], 136 | "text_color": ["#5C3F2D", "#C2B49E"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/pink.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["gray86", "gray17"], 12 | "top_fg_color": ["gray81", "gray20"], 13 | "border_color": ["gray65", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#FF6B6B", "#B74177"], 19 | "hover_color": ["#FF4E4E", "#A01E5C"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#FF99C8", "#692A84"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color":["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#FF6B6B", "#B74177"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#FF6B6B", "#B74177"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#939BA2", "#4A4D50"], 52 | "progress_color": ["#FF6B6B", "#B74177"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#FF6B6B", "#B74177"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#FF4E4E", "#A01E5C"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#939BA2", "#4A4D50"], 72 | "progress_color": ["#FF6B6B", "#B74177"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#939BA2", "#4A4D50"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#FF6B6B", "#B74177"], 83 | "button_hover_color": ["#FF4E4E", "#A01E5C"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#FF6B6B", "#B74177"], 88 | "button_color": ["#FF4E4E", "#A01E5C"], 89 | "button_hover_color": ["#FF354E", "#7D144F"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#FF99C8", "#692A84"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#979DA2", "#565B5E"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#FF99C8", "#C13D98"], 114 | "selected_color": ["#FF6B6B", "#B74177"], 115 | "selected_hover_color": ["#FF4E4E", "#A01E5C"], 116 | "unselected_color": ["#FF99C8", "#C13D98"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#FF99C8", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color":["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/red.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["gray86", "gray17"], 12 | "top_fg_color": ["gray81", "gray20"], 13 | "border_color": ["gray65", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#D03434", "#A11D1D"], 19 | "hover_color": ["#B22E2E", "#791414"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F9F9FA", "#343638"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color":["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#D03434", "#A11D1D"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#D03434", "#A11D1D"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#939BA2", "#4A4D50"], 52 | "progress_color": ["#D03434", "#A11D1D"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#D03434", "#A11D1D"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#B22E2E", "#791414"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#939BA2", "#4A4D50"], 72 | "progress_color": ["#D03434", "#A11D1D"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#939BA2", "#4A4D50"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#D03434", "#A11D1D"], 83 | "button_hover_color": ["#B22E2E", "#791414"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#D03434", "#A11D1D"], 88 | "button_color": ["#B22E2E", "#791414"], 89 | "button_hover_color": ["#942525", "#661818"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F9F9FA", "#343638"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#979DA2", "#565B5E"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#979DA2", "gray29"], 114 | "selected_color": ["#D03434", "#A11D1D"], 115 | "selected_hover_color": ["#B22E2E", "#791414"], 116 | "unselected_color": ["#979DA2", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F9F9FA", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color":["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/rime.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#E0E6E9", "#2B2D2F"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#E0E6E9", "#2B2D2F"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#C0C8CE", "#2D2F33"], 12 | "top_fg_color": ["#B4BCC1", "#2E3135"], 13 | "border_color": ["#8A8F93", "#3D3F42"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#6E8BA4", "#4A5D6B"], 19 | "hover_color": ["#5F7A8E", "#3A4A54"], 20 | "border_color": ["#6C7B82", "#9D9F9F"], 21 | "text_color": ["#F2F7FC", "#F2F7FC"], 22 | "text_color_disabled": ["#B4BCC1", "#A8A8A8"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["#2A2C2F", "#F2F7FC"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F4F7F9", "#2D2F2F"], 33 | "border_color": ["#A3A8AB", "#6C6F71"], 34 | "text_color":["#2A2C2F", "#F2F7FC"], 35 | "placeholder_text_color": ["#9B9F9F", "#A8A8A8"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#6E8BA4", "#4A5D6B"], 41 | "border_color": ["#6C7B82", "#9D9F9F"], 42 | "hover_color": ["#6E8BA4", "#4A5D6B"], 43 | "checkmark_color": ["#F2F7FC", "#B4BCC1"], 44 | "text_color": ["#2A2C2F", "#F2F7FC"], 45 | "text_color_disabled": ["#A8A8A8", "#8A8A8A"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#B0BCC0", "#4A4C4F"], 52 | "progress_color": ["#6E8BA4", "#4A5D6B"], 53 | "button_color": ["#2F2F2F", "#D6D8D9"], 54 | "button_hover_color": ["#1F1F1F", "#E0E0E0"], 55 | "text_color": ["#2A2C2F", "#F2F7FC"], 56 | "text_color_disabled": ["#A8A8A8", "#8A8A8A"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#6E8BA4", "#4A5D6B"], 63 | "border_color": ["#6C7B82", "#9D9F9F"], 64 | "hover_color": ["#5F7A8E", "#3A4A54"], 65 | "text_color": ["#2A2C2F", "#F2F7FC"], 66 | "text_color_disabled": ["#A8A8A8", "#8A8A8A"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#B0BCC0", "#4A4C4F"], 72 | "progress_color": ["#6E8BA4", "#4A5D6B"], 73 | "border_color": ["#D6D6D6", "#D6D6D6"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#B0BCC0", "#4A4C4F"], 81 | "progress_color": ["#B0BCC0", "#D6D8D9"], 82 | "button_color": ["#6E8BA4", "#4A5D6B"], 83 | "button_hover_color": ["#5F7A8E", "#3A4A54"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#6E8BA4", "#4A5D6B"], 88 | "button_color": ["#5F7A8E", "#3A4A54"], 89 | "button_hover_color": ["#4C6B8E", "#2F3B4D"], 90 | "text_color": ["#F2F7FC", "#F2F7FC"], 91 | "text_color_disabled": ["#B4BCC1", "#A8A8A8"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F4F7F9", "#2D2F2F"], 97 | "border_color": ["#A3A8AB", "#6C6F71"], 98 | "button_color": ["#A3A8AB", "#6C6F71"], 99 | "button_hover_color": ["#8A8C8D", "#9B9C9F"], 100 | "text_color": ["#2A2C2F", "#F2F7FC"], 101 | "text_color_disabled": ["#9B9B9B", "#8A8A8A"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["#B4BCC1", "#6C6E71"], 108 | "button_hover_color": ["#9B9B9F", "#8A8C8D"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#A3A8AB", "#3D3F42"], 114 | "selected_color": ["#6E8BA4", "#4A5D6B"], 115 | "selected_hover_color": ["#5F7A8E", "#3A4A54"], 116 | "unselected_color": ["#A3A8AB", "#3D3F42"], 117 | "unselected_hover_color": ["#9B9B9F", "#6C6E71"], 118 | "text_color": ["#F2F7FC", "#F2F7FC"], 119 | "text_color_disabled": ["#B4BCC1", "#A8A8A8"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F4F7F9", "#2D2F2F"], 125 | "border_color": ["#A3A8AB", "#6C6F71"], 126 | "text_color":["#2A2C2F", "#F2F7FC"], 127 | "scrollbar_button_color": ["#B4BCC1", "#6C6E71"], 128 | "scrollbar_button_hover_color": ["#9B9B9F", "#8A8C8D"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["#D0D6DA", "#3D3F42"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["#E0E6E9", "#3D3F42"], 135 | "hover_color": ["#C0C8CE", "#3D3F42"], 136 | "text_color": ["#2A2C2F", "#E0E6E9"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/rose.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#ffc0cb", "#985475"], 12 | "top_fg_color": ["#e9a9c6", "#a05d78"], 13 | "border_color": ["#cb8bb2", "#7c3847"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#a85475", "#6c1f2b"], 19 | "hover_color": ["#98415f", "#5c0f0b"], 20 | "border_color": ["#8b3c49", "#b7707d"], 21 | "text_color": ["#F3EFFF", "#F3EFFF"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#F3EFFF"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#ffd9cb", "#5f172d"], 33 | "border_color": ["#af6d88", "#79344e"], 34 | "text_color": ["gray10", "#F3EFFF"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#a85475", "#6c1f2b"], 41 | "border_color": ["#8b3c49", "#b7707d"], 42 | "hover_color": ["#a85475", "#6c1f2b"], 43 | "checkmark_color": ["#F3EFFF", "#A3A0D2"], 44 | "text_color": ["gray10", "#F3EFFF"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#cf91a1", "#8f4f5e"], 52 | "progress_color": ["#a85475", "#6c1f2b"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#F3EFFF"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#a85475", "#6c1f2b"], 63 | "border_color": ["#8b3c49", "#b7707d"], 64 | "hover_color": ["#98415f", "#5c0f0b"], 65 | "text_color": ["gray10", "#F3EFFF"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#cf91a1", "#8f4f5e"], 72 | "progress_color": ["#a85475", "#6c1f2b"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#cf91a1", "#8f4f5e"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#a85475", "#6c1f2b"], 83 | "button_hover_color": ["#98415f", "#5c0f0b"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#a85475", "#6c1f2b"], 88 | "button_color": ["#98415f", "#5c0f0b"], 89 | "button_hover_color": ["#514075", "#2B1B3C"], 90 | "text_color": ["#F3EFFF", "#F3EFFF"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#ffd9cb", "#5f172d"], 97 | "border_color": ["#af6d88", "#79344e"], 98 | "button_color": ["#837CB9", "#4D437F"], 99 | "button_hover_color": ["#655E8D", "#726A9E"], 100 | "text_color": ["gray10", "#F3EFFF"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#af6d88", "gray29"], 114 | "selected_color": ["#a85475", "#6c1f2b"], 115 | "selected_hover_color": ["#98415f", "#5c0f0b"], 116 | "unselected_color": ["#af6d88", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#F3EFFF", "#F3EFFF"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#ffd9cb", "#1D1E1E"], 125 | "border_color": ["#af6d88", "#79344e"], 126 | "text_color": ["gray10", "#F3EFFF"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/sky.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["#DCE4EE", "#1F6AA5"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["#DCE4EE", "#1F6AA5"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#BBD1E4", "#27577D"], 12 | "top_fg_color": ["#A8C3D9", "#2B6096"], 13 | "border_color": ["#8EADC4", "#36719F"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#1EB44D", "#00891E"], 19 | "hover_color": ["#1AA446", "#006E1B"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F9F9FA", "#343638"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color": ["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#1EB44D", "#00891E"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#1EB44D", "#00891E"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#939BA2", "#4A4D50"], 52 | "progress_color": ["#1EB44D", "#00891E"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#1EB44D", "#00891E"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#1AA446", "#006E1B"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#939BA2", "#4A4D50"], 72 | "progress_color": ["#1EB44D", "#00891E"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#939BA2", "#4A4D50"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#1EB44D", "#00891E"], 83 | "button_hover_color": ["#1AA446", "#006E1B"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#1EB44D", "#00891E"], 88 | "button_color": ["#1AA446", "#006E1B"], 89 | "button_hover_color": ["#166833", "#005F12"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F9F9FA", "#343638"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#979DA2", "#565B5E"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#979DA2", "gray29"], 114 | "selected_color": ["#1EB44D", "#00891E"], 115 | "selected_hover_color": ["#1AA446", "#006E1B"], 116 | "unselected_color": ["#979DA2", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F9F9FA", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color": ["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/violet.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["#D3CFFC", "#6C63A6"], 12 | "top_fg_color": ["#BDB8F7", "#746CA9"], 13 | "border_color": ["#9F9AE3", "#504778"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#7C63A6", "#402E5C"], 19 | "hover_color": ["#6C5090", "#301E3C"], 20 | "border_color": ["#5F4B7A", "#8B7FAE"], 21 | "text_color": ["#F3EFFF", "#F3EFFF"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#F3EFFF"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#EAE8FC", "#33265E"], 33 | "border_color": ["#837CB9", "#4D437F"], 34 | "text_color": ["gray10", "#F3EFFF"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#7C63A6", "#402E5C"], 41 | "border_color": ["#5F4B7A", "#8B7FAE"], 42 | "hover_color": ["#7C63A6", "#402E5C"], 43 | "checkmark_color": ["#F3EFFF", "#A3A0D2"], 44 | "text_color": ["gray10", "#F3EFFF"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#A3A0D2", "#635E8F"], 52 | "progress_color": ["#7C63A6", "#402E5C"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#F3EFFF"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#7C63A6", "#402E5C"], 63 | "border_color": ["#5F4B7A", "#8B7FAE"], 64 | "hover_color": ["#6C5090", "#301E3C"], 65 | "text_color": ["gray10", "#F3EFFF"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#A3A0D2", "#635E8F"], 72 | "progress_color": ["#7C63A6", "#402E5C"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#A3A0D2", "#635E8F"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#7C63A6", "#402E5C"], 83 | "button_hover_color": ["#6C5090", "#301E3C"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#7C63A6", "#402E5C"], 88 | "button_color": ["#6C5090", "#301E3C"], 89 | "button_hover_color": ["#514075", "#2B1B3C"], 90 | "text_color": ["#F3EFFF", "#F3EFFF"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#EAE8FC", "#33265E"], 97 | "border_color": ["#837CB9", "#4D437F"], 98 | "button_color": ["#837CB9", "#4D437F"], 99 | "button_hover_color": ["#655E8D", "#726A9E"], 100 | "text_color": ["gray10", "#F3EFFF"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#837CB9", "gray29"], 114 | "selected_color": ["#7C63A6", "#402E5C"], 115 | "selected_hover_color": ["#6C5090", "#301E3C"], 116 | "unselected_color": ["#837CB9", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#F3EFFF", "#F3EFFF"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#EAE8FC", "#1D1E1E"], 125 | "border_color": ["#837CB9", "#4D437F"], 126 | "text_color": ["gray10", "#F3EFFF"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/themes/yellow.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": ["gray92", "gray14"] 4 | }, 5 | "CTkToplevel": { 6 | "fg_color": ["gray92", "gray14"] 7 | }, 8 | "CTkFrame": { 9 | "corner_radius": 6, 10 | "border_width": 0, 11 | "fg_color": ["gray86", "gray17"], 12 | "top_fg_color": ["gray81", "gray20"], 13 | "border_color": ["gray65", "gray28"] 14 | }, 15 | "CTkButton": { 16 | "corner_radius": 6, 17 | "border_width": 0, 18 | "fg_color": ["#FFA500", "#FF8C00"], 19 | "hover_color": ["#FF8C00", "#FF6600"], 20 | "border_color": ["#3E454A", "#949A9F"], 21 | "text_color": ["#DCE4EE", "#DCE4EE"], 22 | "text_color_disabled": ["gray74", "gray60"] 23 | }, 24 | "CTkLabel": { 25 | "corner_radius": 0, 26 | "fg_color": "transparent", 27 | "text_color": ["gray10", "#DCE4EE"] 28 | }, 29 | "CTkEntry": { 30 | "corner_radius": 6, 31 | "border_width": 2, 32 | "fg_color": ["#F9F9FA", "#343638"], 33 | "border_color": ["#979DA2", "#565B5E"], 34 | "text_color": ["gray10", "#DCE4EE"], 35 | "placeholder_text_color": ["gray52", "gray62"] 36 | }, 37 | "CTkCheckBox": { 38 | "corner_radius": 6, 39 | "border_width": 3, 40 | "fg_color": ["#FFA500", "#FFD700"], 41 | "border_color": ["#3E454A", "#949A9F"], 42 | "hover_color": ["#FFA500", "#FFD700"], 43 | "checkmark_color": ["#DCE4EE", "gray90"], 44 | "text_color": ["gray10", "#DCE4EE"], 45 | "text_color_disabled": ["gray60", "gray45"] 46 | }, 47 | "CTkSwitch": { 48 | "corner_radius": 1000, 49 | "border_width": 3, 50 | "button_length": 0, 51 | "fg_color": ["#FFA500", "#FFD700"], 52 | "progress_color": ["#FF8C00", "#FFA500"], 53 | "button_color": ["gray36", "#D5D9DE"], 54 | "button_hover_color": ["gray20", "gray100"], 55 | "text_color": ["gray10", "#DCE4EE"], 56 | "text_color_disabled": ["gray60", "gray45"] 57 | }, 58 | "CTkRadioButton": { 59 | "corner_radius": 1000, 60 | "border_width_checked": 6, 61 | "border_width_unchecked": 3, 62 | "fg_color": ["#FFA500", "#FFD700"], 63 | "border_color": ["#3E454A", "#949A9F"], 64 | "hover_color": ["#FF8C00", "#FFA500"], 65 | "text_color": ["gray10", "#DCE4EE"], 66 | "text_color_disabled": ["gray60", "gray45"] 67 | }, 68 | "CTkProgressBar": { 69 | "corner_radius": 1000, 70 | "border_width": 0, 71 | "fg_color": ["#FFA500", "#FFD700"], 72 | "progress_color": ["#FF8C00", "#FFA500"], 73 | "border_color": ["gray", "gray"] 74 | }, 75 | "CTkSlider": { 76 | "corner_radius": 1000, 77 | "button_corner_radius": 1000, 78 | "border_width": 6, 79 | "button_length": 0, 80 | "fg_color": ["#FFA500", "#FFD700"], 81 | "progress_color": ["gray40", "#AAB0B5"], 82 | "button_color": ["#FF8C00", "#FFA500"], 83 | "button_hover_color": ["#FF8C00", "#FFA500"] 84 | }, 85 | "CTkOptionMenu": { 86 | "corner_radius": 6, 87 | "fg_color": ["#FFA500", "#FF8C00"], 88 | "button_color": ["#FF8C00", "#FF6600"], 89 | "button_hover_color": ["#27577D", "#FF4500"], 90 | "text_color": ["#DCE4EE", "#DCE4EE"], 91 | "text_color_disabled": ["gray74", "gray60"] 92 | }, 93 | "CTkComboBox": { 94 | "corner_radius": 6, 95 | "border_width": 2, 96 | "fg_color": ["#F9F9FA", "#343638"], 97 | "border_color": ["#979DA2", "#565B5E"], 98 | "button_color": ["#FF8C00", "#FFA500"], 99 | "button_hover_color": ["#6E7174", "#7A848D"], 100 | "text_color": ["gray10", "#DCE4EE"], 101 | "text_color_disabled": ["gray50", "gray45"] 102 | }, 103 | "CTkScrollbar": { 104 | "corner_radius": 1000, 105 | "border_spacing": 4, 106 | "fg_color": "transparent", 107 | "button_color": ["gray55", "gray41"], 108 | "button_hover_color": ["gray40", "gray53"] 109 | }, 110 | "CTkSegmentedButton": { 111 | "corner_radius": 6, 112 | "border_width": 2, 113 | "fg_color": ["#FFA500", "gray29"], 114 | "selected_color": ["#FF8C00", "#FFA500"], 115 | "selected_hover_color": ["#FF8C00", "#FFA500"], 116 | "unselected_color": ["#FFA500", "gray29"], 117 | "unselected_hover_color": ["gray70", "gray41"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkTextbox": { 122 | "corner_radius": 6, 123 | "border_width": 0, 124 | "fg_color": ["#F9F9FA", "#1D1E1E"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "text_color": ["gray10", "#DCE4EE"], 127 | "scrollbar_button_color": ["gray55", "gray41"], 128 | "scrollbar_button_hover_color": ["gray40", "gray53"] 129 | }, 130 | "CTkScrollableFrame": { 131 | "label_fg_color": ["gray78", "gray23"] 132 | }, 133 | "DropdownMenu": { 134 | "fg_color": ["gray90", "gray20"], 135 | "hover_color": ["gray75", "gray28"], 136 | "text_color": ["gray10", "gray90"] 137 | }, 138 | "CTkFont": { 139 | "macOS": { 140 | "family": "SF Display", 141 | "size": 13, 142 | "weight": "normal" 143 | }, 144 | "Windows": { 145 | "family": "Roboto", 146 | "size": 13, 147 | "weight": "normal" 148 | }, 149 | "Linux": { 150 | "family": "Roboto", 151 | "size": 13, 152 | "weight": "normal" 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/core/config/constants.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class App(Enum): 5 | NAME = "EmuHaven" 6 | AUTHOR = "Viren070" 7 | VERSION = "0.14.0a1" 8 | SETTINGS_VERSION = 5 9 | CACHE_VERSION = 2 10 | GH_OWNER = "Viren070" 11 | GH_REPO = "EmuHaven" 12 | GH_ASSET_REGEX = r"EmuHaven.*\.zip" 13 | GITHUB = f"https://github.com/{GH_OWNER}/{GH_REPO}" 14 | DISCORD = "https://discord.viren070.me" 15 | KOFI = "https://ko-fi.com/viren070" 16 | DEFAULT_COLOUR_THEMES = ["blue", "dark-blue", "green"] 17 | VALID_APPEARANCE_MODES = ["dark", "light"] 18 | RESULTS_PER_GAME_PAGE = 20 19 | 20 | 21 | class GitHubOAuth(Enum): 22 | GH_CLIENT_ID = "Iv1.f1a084535d67fabb" 23 | GH_LOGIN_URL = "https://github.com/login/" 24 | TOKEN_REQUEST_URL = GH_LOGIN_URL + "oauth/access_token" 25 | DEVICE_CODE_REQUEST_URL = GH_LOGIN_URL + "device/code" 26 | 27 | 28 | class GitHub(Enum): 29 | API_URL = "https://api.github.com/" 30 | API_RELEASES = API_URL + "repos/{owner}/{repo}/releases" 31 | API_LATEST_RELEASE = API_RELEASES + "/latest" 32 | API_CONTENTS = API_URL + "repos/{owner}/{repo}/contents/{path}" 33 | API_RATE_LIMIT = API_URL + "rate_limit" 34 | API_USER = API_URL + "user" 35 | RAW_URL = "https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}" 36 | 37 | 38 | class Myrient(Enum): 39 | BASE_URL = "https://myrient.erista.me/files/" 40 | 41 | 42 | class Dolphin(Enum): 43 | RELEASE_LIST_URL = "https://dolphin-emu.org/download/list/{}/1/" 44 | DEVELOPMENT_WINDOWS_X64_REGEX = r"dolphin-master-\d+-\d+-x64\.7z" 45 | DEVELOPMENT_WINDOWS_ARM64_REGEX = r"dolphin-master-\d+-\d+-ARM64\.7z" 46 | RELEASE_WINDOWS_X64_REGEX = r"dolphin-\d+-x64\.7z" 47 | RELEASE_WINDOWS_ARM64_REGEX = r"dolphin-\d+-ARM64\.7z" 48 | MYRIENT_GAMECUBE_PATH = "Redump/Nintendo - GameCube - NKit RVZ [zstd-19-128k]/" 49 | MYRIENT_WII_PATH = "Redump/Nintendo - Wii - NKit RVZ [zstd-19-128k]/" 50 | GAME_FILE_EXTENSIONS = [".wbfs", ".iso", ".rvz", ".gcm", ".gcz", ".ciso"] 51 | USER_FOLDERS = [ 52 | "Backup", "Cache", "Config", "Dump", "GameSettings", "GBA", "GC", "Load", "Logs", "Maps", "ResourcePacks", 53 | "SavedAssembly", "ScreenShots", "Shaders", "StateSaves", "Styles", "Themes", "Wii" 54 | ] 55 | 56 | 57 | class Xenia(Enum): 58 | GH_RELEASE_REPO_OWNER = "xenia-project" 59 | GH_RELEASE_REPO_NAME = "release-builds-windows" 60 | GH_RELEASE_ASSET_REGEX = r"xenia_master.zip" 61 | GH_CANARY_RELEASE_REPO_OWNER = "xenia-canary" 62 | GH_CANARY_RELEASE_REPO_NAME = "xenia-canary" 63 | GH_CANARY_RELEASE_ASSET_REGEX = r"xenia_canary.zip" 64 | MYRIENT_XBOX_360_PATH = "Redump/Microsoft - Xbox 360/" 65 | MYRIENT_XBOX_360_DIGITAL_PATH = "No-Intro/Microsoft - Xbox 360 (Digital)/" 66 | USER_FOLDERS = ["xenia.config.toml", "xenia-canary.config.toml", "content"] 67 | 68 | 69 | class Switch(Enum): 70 | FIRMWARE_KEYS_GH_REPO_OWNER = "NXResources" 71 | FIRMWARE_KEYS_GH_REPO_NAME = "NX_FK" 72 | SAVES_GH_REPO_OWNER = "NXResources" 73 | SAVES_GH_REPO_NAME = "NX_Saves" 74 | SAVES_GH_REPO_PATH = "nintendo/switch/savegames" 75 | TITLEDB_GH_REPO_OWNER = "NXResources" 76 | TITLEDB_GH_REPO_NAME = "NX_TitleDB" 77 | TITLEDB_FILENAME = "titles-tiny.US.en.json" 78 | TITLEDB_DOWNLOAD_URL = f"https://github.com/{TITLEDB_GH_REPO_OWNER}/{TITLEDB_GH_REPO_NAME}/releases/download/latest/{TITLEDB_FILENAME}" 79 | TITLEID_BLACKLIST = ["0100000000001009"] 80 | GAMES_URLS = [] 81 | 82 | 83 | class Yuzu(Enum): 84 | GH_RELEASE_REPO_OWNER = "yuzu-emu" 85 | GH_RELEASE_REPO_NAME = "yuzu" 86 | GH_RELEASE_WINDOWS_ASSET_REGEX = r"yuzu-windows-msvc-.*\.zip" 87 | USER_FOLDERS = ["amiibo", "cache", "config", "crash_dumps", "dump", "icons", "keys", "load", "log", "nand", "play_time", "screenshots", "sdmc", "shader", "tas", "sysdata"] 88 | 89 | 90 | class Ryujinx(Enum): 91 | GH_RELEASE_REPO_OWNER = "Ryujinx" 92 | GH_RELEASE_REPO_NAME = "release-channel-master" 93 | GH_RELEASE_WINDOWS_ASSET_REGEX = r'ryujinx-\d+\.\d+\.\d+-win_x64\.zip' 94 | USER_FOLDERS = ["bis", "games", "mods", "profiles", "sdcard", "system", "Config.json"] 95 | 96 | 97 | class Requests(Enum): 98 | USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" 99 | DEFAULT_HEADERS = { 100 | "User-Agent": USER_AGENT, 101 | "Accept-Encoding": "identity", 102 | "Cache-Control": "no-cache", 103 | "Pragma": "no-cache", 104 | } 105 | GH_HEADERS = { 106 | "User-Agent": USER_AGENT, 107 | "Accept-Encoding": "identity", 108 | "Accept": "application/vnd.github+json", 109 | "Authorization": "BEARER {GH_TOKEN}", 110 | "X-GitHub-Api-Version": "2022-11-28", 111 | } 112 | -------------------------------------------------------------------------------- /src/core/config/paths.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import platformdirs 4 | 5 | from core.config import constants 6 | from core.logging.logger import Logger 7 | 8 | 9 | class Paths: 10 | def __init__(self): 11 | portable_mode = Path("PORTABLE.txt").exists() 12 | self.logger = Logger(__name__).get_logger() 13 | self.app_dir = ( 14 | platformdirs.user_data_path( 15 | appauthor=constants.App.AUTHOR.value, appname=constants.App.NAME.value, roaming=True 16 | ) 17 | if not portable_mode 18 | else Path.cwd() / "portable" 19 | ) 20 | self.logger.info("Portable mode: %s", portable_mode) 21 | self.logger.info("App directory: %s", self.app_dir) 22 | self.cache_dir = self.app_dir / "cache" 23 | self.asset_dir = Path(__file__).resolve().parent.parent.parent / "assets" 24 | self.versions_file = self.app_dir / "versions.json" 25 | self.settings_file = self.app_dir / "settings.json" 26 | -------------------------------------------------------------------------------- /src/core/config/versions.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from core.config.paths import Paths 4 | from core.logging.logger import Logger 5 | 6 | 7 | class Versions: 8 | def __init__(self, paths: Paths): 9 | self.logger = Logger(__name__).get_logger() 10 | self.paths = paths 11 | self.versions_dict = {} 12 | self.versions_file = self.paths.versions_file 13 | self.load_versions_from_file() 14 | 15 | def create_versions_file(self): 16 | self.logger.debug("Creating versions file at %s", self.versions_file) 17 | self.versions_file.touch() 18 | with open(self.versions_file, "w", encoding="utf-8") as file: 19 | json.dump({}, file, indent=4) 20 | 21 | def load_versions_from_file(self): 22 | if not self.versions_file.exists(): 23 | return 24 | with open(self.versions_file, "r", encoding="utf-8") as file: 25 | try: 26 | self.versions_dict = json.load(file) 27 | except json.JSONDecodeError: 28 | self.logger.error("Versions file is not a valid JSON file. Recreating the file.") 29 | self.create_versions_file() 30 | self.versions_dict = {} 31 | 32 | def save_versions_to_file(self): 33 | if not self.versions_file.exists(): 34 | self.versions_file.parent.mkdir(parents=True, exist_ok=True) 35 | self.logger.debug("Saving versions to file") 36 | with open(self.versions_file, "w", encoding="utf-8") as file: 37 | json.dump(self.versions_dict, file, indent=4) 38 | 39 | def set_version(self, key, value): 40 | self.logger.info("Setting version for %s to %s", key, value) 41 | self.versions_dict[key] = value 42 | self.save_versions_to_file() 43 | 44 | def get_version(self, key): 45 | self.load_versions_from_file() 46 | self.logger.debug("Getting version for %s", key) 47 | return self.versions_dict.get(key, "") 48 | -------------------------------------------------------------------------------- /src/core/emulators/dolphin/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import platform 3 | 4 | from core.logging.logger import Logger 5 | 6 | 7 | class DolphinSettings: 8 | def __init__(self): 9 | self.logger = Logger(__name__).get_logger() 10 | self.config = { 11 | "release_channel": "release", 12 | "portable_mode": False, 13 | "install_directory": self.get_default_install_directory(), 14 | "game_directory": Path().resolve(), 15 | "sync_user_data": True, 16 | } 17 | 18 | def get_default_install_directory(self): 19 | 20 | system = platform.system().lower() 21 | if system == "windows": 22 | # ~/AppData/Roaming/Dolphin Emulator 23 | return Path.home() / "AppData" / "Local" / "Dolphin Emulator" 24 | 25 | return Path() 26 | 27 | def _set_property(self, property_name, value): 28 | self.logger.debug(f"Setting {property_name} to {value}") 29 | self.config[property_name] = value 30 | 31 | def _get_property(self, property_name): 32 | return self.config.get(property_name) 33 | 34 | portable_mode = property( 35 | fget=lambda self: self._get_property("portable_mode"), 36 | fset=lambda self, value: self._set_property("portable_mode", value), 37 | ) 38 | sync_user_data = property( 39 | fget=lambda self: self._get_property("sync_user_data"), 40 | fset=lambda self, value: self._set_property("sync_user_data", value), 41 | ) 42 | last_used_data_path = property( 43 | fget=lambda self: self._get_property("last_used_data_path"), 44 | fset=lambda self, value: self._set_property("last_used_data_path", value), 45 | ) 46 | install_directory = property( 47 | fget=lambda self: self._get_property("install_directory"), 48 | fset=lambda self, value: self._set_property("install_directory", value), 49 | ) 50 | release_channel = property( 51 | fget=lambda self: self._get_property("release_channel"), 52 | fset=lambda self, value: self._set_property("release_channel", value), 53 | ) 54 | game_directory = property( 55 | fget=lambda self: self._get_property("game_directory"), 56 | fset=lambda self, value: self._set_property("game_directory", value), 57 | ) 58 | -------------------------------------------------------------------------------- /src/core/emulators/ryujinx/settings.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from pathlib import Path 3 | 4 | from core.logging.logger import Logger 5 | 6 | 7 | class RyujinxSettings: 8 | def __init__(self): 9 | self.logger = Logger(__name__).get_logger() 10 | self.config = { 11 | "install_directory": self.get_default_install_directory(), 12 | "portable_mode": False, 13 | "release_channel": "master", 14 | "last_used_data_path": None, 15 | "sync_user_data": True, 16 | } 17 | 18 | def get_default_install_directory(self): 19 | 20 | system = platform.system().lower() 21 | if system == "windows": 22 | return Path.home() / "AppData" / "Local" / "Ryujinx" 23 | 24 | return None 25 | 26 | def _set_property(self, property_name, value): 27 | self.logger.debug(f"Setting {property_name} to {value}") 28 | self.config[property_name] = value 29 | 30 | def _get_property(self, property_name): 31 | return self.config.get(property_name) 32 | 33 | install_directory = property( 34 | fget=lambda self: self._get_property("install_directory"), 35 | fset=lambda self, value: self._set_property("install_directory", value), 36 | ) 37 | portable_mode = property( 38 | fget=lambda self: self._get_property("portable_mode"), 39 | fset=lambda self, value: self._set_property("portable_mode", value), 40 | ) 41 | sync_user_data = property( 42 | fget=lambda self: self._get_property("sync_user_data"), 43 | fset=lambda self, value: self._set_property("sync_user_data", value), 44 | ) 45 | last_used_data_path = property( 46 | fget=lambda self: self._get_property("last_used_data_path"), 47 | fset=lambda self, value: self._set_property("last_used_data_path", value), 48 | ) 49 | release_channel = property( 50 | fget=lambda self: self._get_property("release_channel"), 51 | fset=lambda self, value: self._set_property("release_channel", value), 52 | ) 53 | -------------------------------------------------------------------------------- /src/core/emulators/xenia/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import platform 3 | 4 | from core.logging.logger import Logger 5 | 6 | 7 | class XeniaSettings: 8 | def __init__(self): 9 | self.logger = Logger(__name__).get_logger() 10 | self.config = { 11 | "install_directory": self.get_default_install_directory(), 12 | "portable_mode": False, 13 | "release_channel": "master", 14 | "game_directory": Path().resolve(), 15 | } 16 | 17 | def get_default_install_directory(self): 18 | system = platform.system().lower() 19 | 20 | if system == "windows": 21 | return Path.home() / "AppData" / "Local" / "Xenia" 22 | 23 | return Path() 24 | 25 | def _set_property(self, property_name, value): 26 | self.logger.debug(f"Setting {property_name} to {value}") 27 | self.config[property_name] = value 28 | 29 | def _get_property(self, property_name): 30 | return self.config.get(property_name) 31 | 32 | install_directory = property( 33 | fget=lambda self: self._get_property("install_directory"), 34 | fset=lambda self, value: self._set_property("install_directory", value), 35 | ) 36 | 37 | portable_mode = property( 38 | fget=lambda self: self._get_property("portable_mode"), 39 | fset=lambda self, value: self._set_property("portable_mode", value), 40 | ) 41 | 42 | release_channel = property( 43 | fget=lambda self: self._get_property("release_channel"), 44 | fset=lambda self, value: self._set_property("release_channel", value), 45 | ) 46 | game_directory = property( 47 | fget=lambda self: self._get_property("game_directory"), 48 | fset=lambda self, value: self._set_property("game_directory", value), 49 | ) 50 | -------------------------------------------------------------------------------- /src/core/emulators/yuzu/settings.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from pathlib import Path 3 | from core.logging.logger import Logger 4 | 5 | 6 | class YuzuSettings: 7 | def __init__(self): 8 | self.logger = Logger(__name__).get_logger() 9 | self.config = { 10 | "install_directory": self.get_default_install_directory(), 11 | "portable_mode": False, 12 | "release_channel": "mainline", 13 | "last_used_data_path": None, 14 | "sync_user_data": True, 15 | } 16 | 17 | def get_default_install_directory(self): 18 | system = platform.system().lower() 19 | 20 | if system == "windows": 21 | return Path.home() / "AppData" / "Local" / "yuzu" 22 | 23 | return Path() 24 | 25 | def _set_property(self, property_name, value): 26 | self.logger.debug(f"Setting {property_name} to {value}") 27 | self.config[property_name] = value 28 | 29 | def _get_property(self, property_name): 30 | return self.config.get(property_name) 31 | 32 | install_directory = property( 33 | fget=lambda self: self._get_property("install_directory"), 34 | fset=lambda self, value: self._set_property("install_directory", value), 35 | ) 36 | portable_mode = property( 37 | fget=lambda self: self._get_property("portable_mode"), 38 | fset=lambda self, value: self._set_property("portable_mode", value), 39 | ) 40 | sync_user_data = property( 41 | fget=lambda self: self._get_property("sync_user_data"), 42 | fset=lambda self, value: self._set_property("sync_user_data", value), 43 | ) 44 | release_channel = property( 45 | fget=lambda self: self._get_property("release_channel"), 46 | fset=lambda self, value: self._set_property("release_channel", value), 47 | ) 48 | last_used_data_path = property( 49 | fget=lambda self: self._get_property("last_used_data_path"), 50 | fset=lambda self, value: self._set_property("last_used_data_path", value), 51 | ) 52 | -------------------------------------------------------------------------------- /src/core/logging/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from core.config.constants import App 4 | 5 | 6 | class Logger: 7 | def __init__(self, name, console=True): 8 | # Configure the logger 9 | self.logger = logging.getLogger(name) 10 | self.logger.setLevel(logging.DEBUG) 11 | 12 | # clear handlers to avoid duplicate logs 13 | if self.logger.hasHandlers(): 14 | self.logger.handlers.clear() 15 | 16 | # Create a formatter that includes the timestamp with milliseconds 17 | formatter = logging.Formatter( 18 | '[%(asctime)s.%(msecs)03d] [%(levelname)s] [%(name)s.%(funcName)s]: %(message)s', 19 | datefmt='%Y-%m-%d %H:%M:%S' 20 | ) 21 | 22 | if console: 23 | # Create a console handler and set the log level 24 | console_handler = logging.StreamHandler() 25 | console_handler.setLevel(logging.DEBUG) 26 | console_handler.setFormatter(formatter) 27 | self.logger.addHandler(console_handler) 28 | # create a file handler and set the log level 29 | try: 30 | file_handler = logging.FileHandler(f"{App.NAME.value}.log") 31 | file_handler.setLevel(logging.DEBUG) 32 | file_handler.setFormatter(formatter) 33 | self.logger.addHandler(file_handler) 34 | except PermissionError: 35 | self.logger.error("Permission denied to write to log file") 36 | 37 | def get_logger(self): 38 | return self.logger 39 | -------------------------------------------------------------------------------- /src/core/network/myrient.py: -------------------------------------------------------------------------------- 1 | from core.config.constants import Myrient 2 | from core.network.web import get_all_files_from_page 3 | from urllib.parse import quote, unquote 4 | 5 | 6 | def get_list_of_games(myrient_path): 7 | scrape_result = get_all_files_from_page(url=Myrient.BASE_URL.value + myrient_path, file_ext=".zip") 8 | if not scrape_result["status"]: 9 | return scrape_result 10 | scrape_result["games"] = [] 11 | for file in scrape_result["files"]: 12 | # Remove the base URL and the myrient path from the filename 13 | # this will return a list of filenames that are relative to the myrient path 14 | scrape_result["games"].append(unquote(file.replace(Myrient.BASE_URL.value + myrient_path, "").replace(".zip", ""))) 15 | 16 | return scrape_result 17 | 18 | 19 | def get_game_download_url(game_name, myrient_path): 20 | return Myrient.BASE_URL.value + myrient_path + f"{quote(game_name)}.zip" 21 | -------------------------------------------------------------------------------- /src/core/utils/files.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import zipfile 3 | 4 | from core.utils.progress_handler import ProgressHandler 5 | 6 | 7 | def copy_directory_with_progress(source_dir, target_dir, progress_handler=None, exclude=None, include=None): 8 | if progress_handler is None: 9 | progress_handler = ProgressHandler() 10 | if not source_dir.exists() or not source_dir.is_dir(): 11 | progress_handler.report_error(f"Path does not exist or is not a directory: {source_dir}") 12 | return {"status": False, "message": f"Path does not exist or is not a directory: {source_dir}"} 13 | 14 | # Get a list of all files and folders in the source directory 15 | all_files = [] 16 | for root, dirs, files in source_dir.walk(): 17 | all_files.extend([root / file for file in files]) 18 | 19 | if include: 20 | all_files = [file for file in all_files if any( 21 | incl_folder in file.parts for incl_folder in include)] 22 | elif exclude: 23 | all_files = [file for file in all_files if not any( 24 | excl_folder in file.parts for excl_folder in exclude)] 25 | # Get the total number of files to copy 26 | total_files = len(all_files) 27 | progress_handler.set_total_units(total_files) 28 | 29 | # Create the target directory if it doesn't exist 30 | target_dir.mkdir(parents=True, exist_ok=True) 31 | 32 | # Copy files from source to target directory and display progress 33 | copied_files = 0 34 | for file in all_files: 35 | if progress_handler.should_cancel(): 36 | progress_handler.cancel() 37 | return {"status": False, "message": "Copy operation cancelled"} 38 | 39 | target_file = target_dir / file.relative_to(source_dir) 40 | 41 | target_dirname = target_file.parent 42 | 43 | # Create the necessary directories in the target if they don't exist 44 | target_dirname.mkdir(parents=True, exist_ok=True) 45 | 46 | # Copy the file to the target directory 47 | shutil.copy2(file, target_file) 48 | 49 | copied_files += 1 50 | progress_handler.report_progress(copied_files) 51 | 52 | progress_handler.report_success() 53 | return { 54 | "status": True, 55 | "message": "Files copied successfully", 56 | } 57 | 58 | 59 | def extract_zip_archive_with_progress(zip_path, extract_directory, progress_handler): 60 | extracted_files = [] 61 | if progress_handler is None: 62 | progress_handler = ProgressHandler() 63 | rollback_needed = False 64 | try: 65 | with zipfile.ZipFile(zip_path, 'r') as archive: 66 | total_files = len(archive.namelist()) 67 | progress_handler.set_total_units(total_files) 68 | for file in archive.namelist(): 69 | if progress_handler.should_cancel(): 70 | rollback_needed = True 71 | break 72 | archive.extract(file, extract_directory) 73 | extracted_files.append(file) 74 | # Calculate and display progress 75 | progress_handler.report_progress(len(extracted_files)) 76 | except zipfile.BadZipFile as error: 77 | progress_handler.report_error(error) 78 | return {"status": False, "message": "The ZIP file is corrupted or invalid"} 79 | except Exception as error: 80 | progress_handler.report_error(error) 81 | return {"status": False, "message": error} 82 | 83 | if rollback_needed: 84 | progress_handler.cancel() 85 | dirs = [] 86 | for file in extracted_files: 87 | file_path = extract_directory / file 88 | if file_path.is_dir(): 89 | dirs.append(file_path) 90 | if file_path.is_file(): 91 | file_path.unlink(missing_ok=True) 92 | for dir in dirs: 93 | try: 94 | dir.rmdir() 95 | except OSError: 96 | pass 97 | zip_path.unlink(missing_ok=True) 98 | return {"status": False, "message": "Extraction cancelled"} 99 | progress_handler.report_success() 100 | return {"status": True, "message": "Extraction successful", "extracted_files": extracted_files} 101 | -------------------------------------------------------------------------------- /src/core/utils/progress_handler.py: -------------------------------------------------------------------------------- 1 | class ProgressHandler: 2 | """ 3 | Template class for progress handlers. 4 | 5 | Progress handlers are used to report progress, success, and errors to the user. 6 | They are used by functions that take a long time to complete, so that the user 7 | can see what is happening. 8 | 9 | The default implementation of this class does nothing. Subclasses should override 10 | the methods. 11 | 12 | Subclasses can use a GUI based progress handler to show a progress bar, or a text 13 | based progress handler to show a text display. 14 | 15 | Subclasses can also use a progress handler to implement a cancel button, so that 16 | the user can cancel the operation if it is taking too long. The should_cancel method 17 | will be called periodically to check if the user has pressed the cancel button within 18 | the functions and the cancel method will be called from these functions. 19 | """ 20 | def report_progress(self, value): 21 | pass 22 | 23 | def report_success(self): 24 | pass 25 | 26 | def report_error(self, error): 27 | pass 28 | 29 | def set_total_units(self, total_units): 30 | pass 31 | 32 | def is_total_units_set(self): 33 | return True 34 | 35 | def should_cancel(self): 36 | pass 37 | 38 | def cancel(self): 39 | pass 40 | -------------------------------------------------------------------------------- /src/gui/frames/emulator_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | from customtkinter import ThemeManager 3 | 4 | 5 | class EmulatorFrame(customtkinter.CTkFrame): 6 | def __init__(self, parent_frame, paths, settings, versions, assets): 7 | super().__init__(parent_frame, corner_radius=0, bg_color="transparent") 8 | self.settings = settings 9 | self.metadata = self.versions = versions 10 | self.paths = paths 11 | self.assets = assets 12 | self.parent_frame = parent_frame 13 | self.build_frame() 14 | 15 | def build_frame(self): 16 | self.grid_columnconfigure(1, weight=1) 17 | self.grid_rowconfigure(0, weight=1) 18 | # create yuzu navigation frame 19 | self.navigation_frame = customtkinter.CTkFrame(self, corner_radius=0, width=20, border_width=2) 20 | self.navigation_frame.grid(row=0, column=0, sticky="nsew") 21 | # create yuzu menu buttons 22 | text_color = ThemeManager.theme["CTkLabel"]["text_color"] 23 | self.start_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, width=120, height=25, image=self.assets.play_image, border_spacing=10, text="Play", 24 | fg_color="transparent", text_color=text_color, 25 | anchor="w", command=self.start_button_event) 26 | self.start_button.grid(row=1, column=0, sticky="ew", padx=2, pady=(2, 0)) 27 | 28 | self.manage_data_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, width=120, height=25, border_spacing=10, text="Manage Data", 29 | fg_color="transparent", text_color=text_color, 30 | anchor="w", command=self.manage_data_button_event) 31 | 32 | self.manage_data_button.grid(row=2, column=0, padx=2, sticky="ew") 33 | 34 | self.manage_games_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, width=120, height=25, border_spacing=10, text="Manage Games", 35 | fg_color="transparent", text_color=text_color, 36 | anchor="w", command=self.manage_games_button_event) 37 | self.manage_games_button.grid(row=3, column=0, padx=2, sticky="ew") 38 | 39 | self.start_frame = None 40 | self.manage_data_frame = None 41 | self.manage_games_frame = None 42 | 43 | def start_button_event(self): 44 | self.select_frame_by_name("start") 45 | 46 | def manage_data_button_event(self): 47 | self.select_frame_by_name("data") 48 | 49 | def manage_games_button_event(self): 50 | self.select_frame_by_name("games") 51 | 52 | def select_frame_by_name(self, name): 53 | self.start_button.configure(fg_color=self.start_button.cget("hover_color") if name == "start" else "transparent") 54 | self.manage_data_button.configure(fg_color=self.manage_data_button.cget("hover_color") if name == "data" else "transparent") 55 | self.manage_games_button.configure(fg_color=self.manage_games_button.cget("hover_color") if name == "games" else "transparent") 56 | if name == "start": 57 | self.start_frame.grid(row=0, column=1, sticky="nsew") 58 | else: 59 | self.start_frame.grid_forget() 60 | if name == "data": 61 | self.manage_data_frame.grid(row=0, column=1, sticky="nsew") 62 | else: 63 | self.manage_data_frame.grid_forget() 64 | if name == "games": 65 | self.manage_games_frame.grid(row=0, column=1, sticky="nsew") 66 | else: 67 | self.manage_games_frame.grid_forget() 68 | -------------------------------------------------------------------------------- /src/gui/frames/my_games_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.game_list_frame import GameListFrame 4 | from gui.libs.CTkMessagebox import messagebox 5 | 6 | 7 | class MyGamesFrame(GameListFrame): 8 | def __init__(self, master, event_manager, emulator_settings_object, game_extensions, scan_subdirectories=False): 9 | self.scan_subdirectories = scan_subdirectories 10 | self.total_pages = None 11 | self.emulator_settings = emulator_settings_object 12 | self.game_extensions = game_extensions 13 | self.update_in_progress = False 14 | super().__init__(master=master, event_manager=event_manager) 15 | 16 | def get_game_list(self): 17 | games = [] 18 | if not (self.emulator_settings.game_directory.exists() and self.emulator_settings.game_directory.is_dir()): 19 | return [] 20 | if self.scan_subdirectories: 21 | return self.get_current_roms_from_subdirectories() 22 | for file in self.emulator_settings.game_directory .iterdir(): 23 | if file.is_file() and file.suffix in self.game_extensions: 24 | # Create a ROMFile object and append it to the roms list 25 | games.append(file.name) 26 | 27 | return { 28 | "result": (games,), 29 | } 30 | 31 | def add_game_to_frame(self, game, row_counter): 32 | def convert_bytes_to_suitable_unit(bytes): 33 | if bytes < 1024: 34 | return f"{bytes} B" 35 | elif bytes < 1024 * 1024: 36 | return f"{bytes / 1024:.2f} KB" 37 | elif bytes < 1024 * 1024 * 1024: 38 | return f"{bytes / 1024 / 1024:.2f} MB" 39 | elif bytes < 1024 * 1024 * 1024 * 1024: 40 | return f"{bytes / 1024 / 1024 / 1024:.2f} GB" 41 | else: 42 | return f"{bytes / 1024 / 1024 / 1024 / 1024:.2f} TB" 43 | 44 | game_frame = customtkinter.CTkFrame(self.result_frame, corner_radius=7, border_width=1, fg_color="transparent", height=200) 45 | game_frame.grid(row=row_counter, column=0, sticky="ew", pady=10, padx=10) 46 | game_frame.grid_columnconfigure(0, weight=1) 47 | 48 | game_label = customtkinter.CTkLabel(game_frame, text=game, font=("Arial", 15), anchor="w") 49 | game_label.grid(row=0, column=0, sticky="nsew", padx=5, pady=10) 50 | 51 | game_size = (self.emulator_settings.game_directory / game).stat().st_size 52 | game_size_label = customtkinter.CTkLabel(game_frame, text=convert_bytes_to_suitable_unit(game_size), font=("Arial", 12), anchor="w") 53 | game_size_label.grid(row=0, column=1, sticky="nsew", padx=5, pady=10) 54 | 55 | delete_button = customtkinter.CTkButton(game_frame, text="Delete", width=100, command=lambda: self.delete_game(game)) 56 | delete_button.grid(row=0, column=2, padx=5, pady=2) 57 | 58 | def get_current_roms_from_subdirectories(self): 59 | games = [] 60 | 61 | for _, _, files in self.emulator_settings.game_directory.walk(): 62 | for file in files: 63 | if file.suffix in self.game_extensions and file.is_file(): 64 | games.append(file.name) 65 | return { 66 | "result": games, 67 | } 68 | 69 | def delete_game(self, game): 70 | game_path = self.emulator_settings.game_directory / game 71 | if not game_path.exists(): 72 | self.get_game_list_button_event() 73 | if messagebox.askyesno(self.winfo_toplevel(), "Delete ROM", f"This will delete the game located at: \n\n{game_path}\nThis action cannot be undone. Are you sure you wish to continue?", icon="warning") != "yes": 74 | return 75 | try: 76 | game_path.unlink() 77 | except Exception as error: 78 | messagebox.showerror(self.winfo_toplevel(), "Delete Game", f"An error prevented the game from being deleted:\n\n{error}") 79 | self.get_game_list_button_event() 80 | -------------------------------------------------------------------------------- /src/gui/frames/myrient_game_list_frame.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | import customtkinter 4 | 5 | from core.logging.logger import Logger 6 | from core.network.myrient import get_game_download_url, get_list_of_games 7 | from gui.frames.game_list_frame import GameListFrame 8 | from gui.libs.CTkMessagebox import messagebox 9 | 10 | 11 | class MyrientGameListFrame(GameListFrame): 12 | def __init__(self, master, event_manager, cache, myrient_path, console_name, download_button_event): 13 | self.myrient_path = myrient_path 14 | self.console_name = console_name 15 | self.cache = cache 16 | self.download_button_event = download_button_event 17 | super().__init__(master=master, event_manager=event_manager) 18 | self.logger = Logger(__name__).get_logger() 19 | 20 | def get_game_list(self): 21 | cache_lookup_result = self.cache.get_json(f"{self.console_name}_games") 22 | if cache_lookup_result["status"]: 23 | self.logger.info("game list cache hit") 24 | game_list = cache_lookup_result["data"] 25 | return { 26 | "result": (game_list, ), 27 | "message": { 28 | "function": messagebox.showsuccess, 29 | "arguments": (self.winfo_toplevel(), "Success", "Successfully retrieved games from cache.") 30 | } 31 | } 32 | self.logger.info("game list cache miss") 33 | scrape_result = get_list_of_games(myrient_path=self.myrient_path) 34 | if not scrape_result["status"]: 35 | return { 36 | "result": ([], ), 37 | "message": { 38 | "function": messagebox.showerror, 39 | "arguments": (self.winfo_toplevel(), "Error", "Failed to fetch games.") 40 | } 41 | } 42 | self.cache.add_json(f"{self.console_name}_games", scrape_result["games"]) 43 | return { 44 | "result": (scrape_result["games"],), 45 | "message": { 46 | "function": messagebox.showsuccess, 47 | "arguments": (self.winfo_toplevel(), "Success", "Successfully fetched games.") 48 | } 49 | } 50 | 51 | def add_game_to_frame(self, game, row_counter): 52 | game_download_url = get_game_download_url(game_name=game, myrient_path=self.myrient_path) 53 | entry = customtkinter.CTkEntry(self.result_frame, width=400) 54 | entry.insert(0, game) 55 | entry.configure(state="disabled") 56 | 57 | entry.grid(row=row_counter, column=0, padx=10, pady=5, sticky="w") 58 | button = customtkinter.CTkButton(self.result_frame, text="Download") 59 | button.configure(command=lambda button=button, game=game: self.download_button_event(game, self.myrient_path)) 60 | button.bind("", lambda event, game=game: webbrowser.open(game_download_url)) 61 | button.grid(row=row_counter, column=1, padx=10, pady=5, sticky="e") 62 | -------------------------------------------------------------------------------- /src/gui/frames/ryujinx/ryujinx_games_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.my_switch_games_frame import MySwitchGamesFrame 4 | 5 | 6 | class RyujinxROMFrame(customtkinter.CTkTabview): 7 | def __init__(self, master, settings, cache): 8 | super().__init__(master, corner_radius=7, anchor="nw") 9 | self.emulator = master.ryujinx 10 | self.event_manager = master.event_manager 11 | self.assets = master.assets 12 | self.master = master 13 | self.roms = None 14 | self.cache = cache 15 | self.settings = settings 16 | self.results_per_page = 10 17 | self.update_in_progress = False 18 | self.downloads_in_progress = 0 19 | self.build_frame() 20 | 21 | def build_frame(self): 22 | 23 | self.grid(row=0, column=0, sticky="nsew") 24 | self.add("My ROMs") 25 | self.tab("My ROMs").grid_columnconfigure(0, weight=1) 26 | self.tab("My ROMs").grid_rowconfigure(0, weight=1) 27 | 28 | self.current_roms_frame = MySwitchGamesFrame(self.tab("My ROMs"), cache=self.cache, assets=self.assets, event_manager=self.event_manager, emulator_name="ryujinx", emulator_object=self.emulator) 29 | self.current_roms_frame.grid(row=0, column=0, sticky="nsew") 30 | -------------------------------------------------------------------------------- /src/gui/frames/settings/dolphin_settings_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.settings.setting_modal import SettingModal 4 | 5 | 6 | class DolphinSettingsFrame(customtkinter.CTkFrame): 7 | def __init__(self, parent_frame, settings): 8 | super().__init__(parent_frame, corner_radius=0, fg_color="transparent") 9 | self.settings = settings 10 | self.build_frame() 11 | 12 | def build_frame(self): 13 | self.grid_columnconfigure(0, weight=1) 14 | 15 | install_directory_setting = SettingModal( 16 | master=self, 17 | settings=self.settings, 18 | setting_options={ 19 | "object": self.settings.dolphin, 20 | "id": "install_directory", 21 | "type": "path", 22 | "title": "Install Directory", 23 | "description": "The directory where Dolphin is installed.", 24 | }, 25 | path_options={ 26 | "type": "directory", 27 | "title": "Select Dolphin Install Directory", 28 | } 29 | ) 30 | 31 | game_directory_setting = SettingModal( 32 | master=self, 33 | settings=self.settings, 34 | setting_options={ 35 | "object": self.settings.dolphin, 36 | "id": "game_directory", 37 | "type": "path", 38 | "title": "Game Directory", 39 | "description": "The directory where your games are stored.", 40 | }, 41 | path_options={ 42 | "type": "directory", 43 | "title": "Select Game Directory", 44 | } 45 | ) 46 | 47 | portable_mode_setting = SettingModal( 48 | master=self, 49 | settings=self.settings, 50 | setting_options={ 51 | "object": self.settings.dolphin, 52 | "id": "portable_mode", 53 | "type": "switch", 54 | "title": "Portable Mode", 55 | "description": "Enable portable mode for Dolphin.", 56 | }, 57 | ) 58 | 59 | sync_user_data_setting = SettingModal( 60 | master=self, 61 | settings=self.settings, 62 | setting_options={ 63 | "object": self.settings.dolphin, 64 | "id": "sync_user_data", 65 | "type": "switch", 66 | "title": "Sync User Data", 67 | "description": "Keep your user data synced as you switch between portable and non-portable mode.", 68 | }, 69 | ) 70 | 71 | install_directory_setting.grid(row=0, column=0, padx=10, pady=5, sticky="ew") 72 | game_directory_setting.grid(row=1, column=0, padx=10, pady=5, sticky="ew") 73 | portable_mode_setting.grid(row=2, column=0, padx=10, pady=5, sticky="ew") 74 | sync_user_data_setting.grid(row=3, column=0, padx=10, pady=5, sticky="ew") 75 | -------------------------------------------------------------------------------- /src/gui/frames/settings/ryujinx_settings_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.settings.setting_modal import SettingModal 4 | 5 | 6 | class RyujinxSettingsFrame(customtkinter.CTkFrame): 7 | def __init__(self, parent_frame, settings): 8 | super().__init__(parent_frame, corner_radius=0, fg_color="transparent") 9 | self.settings = settings 10 | self.build_frame() 11 | 12 | def build_frame(self): 13 | self.grid_columnconfigure(0, weight=1) 14 | 15 | install_directory_setting = SettingModal( 16 | master=self, 17 | settings=self.settings, 18 | setting_options={ 19 | "object": self.settings.ryujinx, 20 | "id": "install_directory", 21 | "type": "path", 22 | "title": "Install Directory", 23 | "description": "The directory where Ryujinx is installed.", 24 | }, 25 | path_options={ 26 | "type": "directory", 27 | "title": "Select Ryujinx Install Directory", 28 | } 29 | ) 30 | install_directory_setting.grid(row=0, column=0, padx=10, pady=5, sticky="ew") 31 | 32 | portable_mode_setting = SettingModal( 33 | master=self, 34 | settings=self.settings, 35 | setting_options={ 36 | "object": self.settings.ryujinx, 37 | "id": "portable_mode", 38 | "type": "switch", 39 | "title": "Portable Mode", 40 | "description": "Enable portable mode for Ryujinx.", 41 | }, 42 | ) 43 | portable_mode_setting.grid(row=1, column=0, padx=10, pady=5, sticky="ew") 44 | 45 | sync_user_data_setting = SettingModal( 46 | master=self, 47 | settings=self.settings, 48 | setting_options={ 49 | "object": self.settings.ryujinx, 50 | "id": "sync_user_data", 51 | "type": "switch", 52 | "title": "Sync User Data", 53 | "description": "Keep your user data synced as you switch between portable and non-portable mode.", 54 | }, 55 | ) 56 | sync_user_data_setting.grid(row=2, column=0, padx=10, pady=5, sticky="ew") 57 | -------------------------------------------------------------------------------- /src/gui/frames/settings/xenia_settings_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.settings.setting_modal import SettingModal 4 | 5 | 6 | class XeniaSettingsFrame(customtkinter.CTkFrame): 7 | def __init__(self, parent_frame, settings): 8 | super().__init__(parent_frame, corner_radius=0, fg_color="transparent") 9 | self.settings = settings 10 | self.build_frame() 11 | 12 | def build_frame(self): 13 | self.grid_columnconfigure(0, weight=1) 14 | 15 | install_directory_setting = SettingModal( 16 | master=self, 17 | settings=self.settings, 18 | setting_options={ 19 | "object": self.settings.xenia, 20 | "id": "install_directory", 21 | "type": "path", 22 | "title": "Install Directory", 23 | "description": "The directory where Xenia is installed.", 24 | }, 25 | path_options={ 26 | "type": "directory", 27 | "title": "Select Xenia Install Directory", 28 | } 29 | ) 30 | install_directory_setting.grid(row=0, column=0, padx=10, pady=5, sticky="ew") 31 | 32 | game_directory_setting = SettingModal( 33 | master=self, 34 | settings=self.settings, 35 | setting_options={ 36 | "object": self.settings.xenia, 37 | "id": "game_directory", 38 | "type": "path", 39 | "title": "Game Directory", 40 | "description": "The directory where your games are stored.", 41 | }, 42 | path_options={ 43 | "type": "directory", 44 | "title": "Select Game Directory", 45 | } 46 | ) 47 | game_directory_setting.grid(row=1, column=0, padx=10, pady=5, sticky="ew") 48 | 49 | portable_mode_setting = SettingModal( 50 | master=self, 51 | settings=self.settings, 52 | setting_options={ 53 | "object": self.settings.xenia, 54 | "id": "portable_mode", 55 | "type": "switch", 56 | "title": "Portable Mode", 57 | "description": "Enable portable mode for Xenia.", 58 | }, 59 | ) 60 | portable_mode_setting.grid(row=2, column=0, padx=10, pady=5, sticky="ew") 61 | -------------------------------------------------------------------------------- /src/gui/frames/settings/yuzu_settings_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.settings.setting_modal import SettingModal 4 | 5 | 6 | class YuzuSettingsFrame(customtkinter.CTkFrame): 7 | def __init__(self, parent_frame, settings): 8 | super().__init__(parent_frame, corner_radius=0, fg_color="transparent") 9 | self.settings = settings 10 | self.build_frame() 11 | 12 | def build_frame(self): 13 | # give 1st column a weight of 1 so it fills all available space 14 | self.grid_columnconfigure(0, weight=1) 15 | 16 | install_directory_setting = SettingModal( 17 | master=self, 18 | settings=self.settings, 19 | setting_options={ 20 | "object": self.settings.yuzu, 21 | "id": "install_directory", 22 | "type": "path", 23 | "title": "Install Directory", 24 | "description": "The directory where Yuzu is installed.", 25 | }, 26 | path_options={ 27 | "type": "directory", 28 | "title": "Select Yuzu Install Directory", 29 | } 30 | ) 31 | install_directory_setting.grid(row=0, column=0, padx=10, pady=5, sticky="ew") 32 | 33 | portable_mode_setting = SettingModal( 34 | master=self, 35 | settings=self.settings, 36 | setting_options={ 37 | "object": self.settings.yuzu, 38 | "id": "portable_mode", 39 | "type": "switch", 40 | "title": "Portable Mode", 41 | "description": "Enable portable mode for Yuzu.", 42 | }, 43 | ) 44 | portable_mode_setting.grid(row=1, column=0, padx=10, pady=5, sticky="ew") 45 | 46 | sync_user_data_setting = SettingModal( 47 | master=self, 48 | settings=self.settings, 49 | setting_options={ 50 | "object": self.settings.yuzu, 51 | "id": "sync_user_data", 52 | "type": "switch", 53 | "title": "Sync User Data", 54 | "description": "Sync your user data across mainline and early access versions and between portable and non-portable modes.", 55 | }, 56 | ) 57 | sync_user_data_setting.grid(row=2, column=0, padx=10, pady=5, sticky="ew") 58 | -------------------------------------------------------------------------------- /src/gui/frames/yuzu/yuzu_games_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.frames.my_switch_games_frame import MySwitchGamesFrame 4 | 5 | 6 | class YuzuGamesFrame(customtkinter.CTkTabview): 7 | def __init__(self, master, settings, cache, event_manager): 8 | super().__init__(master, corner_radius=7, anchor="nw") 9 | self.emulator = master.yuzu 10 | self.master = master 11 | self.assets = master.assets 12 | self.roms = None 13 | self.settings = settings 14 | self.cache = cache 15 | self.event_manager = event_manager 16 | 17 | self.update_in_progress = False 18 | self.downloads_in_progress = 0 19 | self.build_frame() 20 | 21 | def build_frame(self): 22 | 23 | self.grid(row=0, column=0, sticky="nsew") 24 | self.add("My ROMs") 25 | self.tab("My ROMs").grid_columnconfigure(0, weight=1) 26 | self.tab("My ROMs").grid_rowconfigure(0, weight=1) 27 | 28 | self.current_roms_frame = MySwitchGamesFrame(self.tab("My ROMs"), cache=self.cache, assets=self.assets, event_manager=self.event_manager, emulator_name="yuzu", emulator_object=self.emulator) 29 | self.current_roms_frame.grid(row=0, column=0, sticky="nsew") 30 | -------------------------------------------------------------------------------- /src/gui/handlers/progress/progress_frame.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | 4 | class ProgressFrame(customtkinter.CTkFrame): 5 | def __init__(self, master, handler): 6 | super().__init__(master, border_width=1) 7 | self.handler = handler 8 | self.grid_columnconfigure(0, weight=1) 9 | self.smoothing_factor = 0.3 10 | self.cancel_download_raised = False 11 | 12 | self.operation_title = customtkinter.CTkLabel(self) 13 | self.progress_label = customtkinter.CTkLabel(self) 14 | self.progress_bar = customtkinter.CTkProgressBar( 15 | self, orientation="horizontal", mode="determinate" 16 | ) 17 | self.percentage_label = customtkinter.CTkLabel(self, text="0%") 18 | self.speed_label = customtkinter.CTkLabel(self, text="0 MB/s") 19 | 20 | self.status_label = customtkinter.CTkLabel( 21 | self, width=100, text="Status: Downloading..." 22 | ) 23 | self.eta_label = customtkinter.CTkLabel(self, text="Time Left: 00:00:00") 24 | self.cancel_operation_button = customtkinter.CTkButton( 25 | self, text="Cancel", command=self.cancel_button_event 26 | ) 27 | 28 | self.operation_title.grid(row=0, column=0, sticky="W", padx=10, pady=5) 29 | 30 | self.progress_label.grid(row=1, column=0, sticky="W", padx=10) 31 | self.progress_bar.grid(row=2, column=0, columnspan=6, padx=(10, 45), pady=5, sticky="EW") 32 | self.percentage_label.grid(row=2, column=5, sticky="E", padx=10) 33 | self.speed_label.grid(row=1, column=5, sticky="E", padx=10) 34 | self.status_label.grid(row=3, column=0, sticky="W", padx=10, pady=5) 35 | self.eta_label.grid(row=0, column=5, sticky="E", pady=5, padx=10) 36 | self.cancel_operation_button.grid(row=3, column=5, pady=10, padx=10, sticky="E") 37 | 38 | def set_title(self, title): 39 | self.operation_title.configure(text=title) 40 | 41 | def update_progress(self, units_complete, total_units, measurement_unit): 42 | self.progress_label.configure(text=f"{units_complete:.0f}{measurement_unit} / {total_units:.0f}{measurement_unit}") 43 | progress = units_complete / total_units if total_units > 0 else 0 44 | self.progress_bar.set(progress) 45 | self.percentage_label.configure(text=f"{progress*100:.0f}%") 46 | 47 | def set_speed(self, speed, measurement_unit, time_measurement="s"): 48 | self.speed_label.configure(text=f"{speed:.0f}{measurement_unit}/{time_measurement}") 49 | 50 | def set_eta(self, eta): 51 | self.eta_label.configure(text=f"Time Left: {eta}") 52 | 53 | def set_status(self, status): 54 | self.status_label.configure(text=f"Status: {status}") 55 | 56 | def set_cancel_button_state(self, state): 57 | self.cancel_operation_button.configure(state=state) 58 | 59 | def cancel_button_event(self): 60 | self.handler.send_cancel_signal_to_operation() 61 | 62 | def show(self): 63 | self.grid(row=0, column=0, sticky="ew") 64 | 65 | def hide(self): 66 | self.grid_forget() 67 | -------------------------------------------------------------------------------- /src/gui/handlers/progress/progress_window.py: -------------------------------------------------------------------------------- 1 | import customtkinter 2 | 3 | from gui.handlers.progress.progress_frame import ProgressFrame 4 | 5 | 6 | class ProgressWindow(customtkinter.CTkToplevel): 7 | def __init__(self, master, handler): 8 | super().__init__(master) 9 | self.grid_columnconfigure(0, weight=1) 10 | self._progress_frame = ProgressFrame(self, handler) 11 | self._progress_frame.grid(row=0, column=0, sticky="ew") 12 | self.resizable(False, False) 13 | self.withdraw() 14 | self.protocol("WM_DELETE_WINDOW", self.on_close) 15 | 16 | def on_close(self): 17 | self.cancel_button_event() 18 | 19 | def set_title(self, title): 20 | self._progress_frame.operation_title.configure(text=title) 21 | self.title(title) 22 | 23 | def update_progress(self, units_complete, total_units, measurement_unit): 24 | self._progress_frame.update_progress(units_complete, total_units, measurement_unit) 25 | 26 | def set_speed(self, speed, measurement_unit, time_measurement="s"): 27 | self._progress_frame.set_speed(speed, measurement_unit, time_measurement) 28 | 29 | def set_eta(self, eta): 30 | self._progress_frame.set_eta(eta) 31 | 32 | def set_status(self, status): 33 | self._progress_frame.set_status(status) 34 | 35 | def set_cancel_button_state(self, state): 36 | self._progress_frame.set_cancel_button_state(state) 37 | 38 | def cancel_button_event(self): 39 | self._progress_frame.cancel_button_event() 40 | 41 | def show(self): 42 | self.deiconify() 43 | 44 | def hide(self): 45 | self.withdraw() 46 | -------------------------------------------------------------------------------- /src/gui/handlers/thread_event_manager.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import threading 3 | import traceback 4 | 5 | from core.logging.logger import Logger 6 | 7 | 8 | class ThreadEventManager: 9 | def __init__(self, window): 10 | self.logger = Logger(__name__).get_logger() 11 | self.events = [] 12 | self.window = window 13 | self.result_queue = queue.Queue() 14 | 15 | def is_event_running(self, event_id): 16 | for event in self.events: 17 | if event["id"] == event_id: 18 | return True 19 | return False 20 | 21 | def add_event(self, event_id, func, kwargs=None, completion_functions=None, error_functions=None, completion_funcs_with_result=None, ignore_messages=False): 22 | event = { 23 | "id": event_id, 24 | "function": func, 25 | "kwargs": kwargs if kwargs else {}, 26 | "completion_functions": completion_functions if completion_functions else [], 27 | "completion_func_with_result": completion_funcs_with_result if completion_funcs_with_result else [], 28 | "error_functions": error_functions if error_functions else [], 29 | "ignore_messages": ignore_messages, 30 | "output_queue": queue.Queue(), 31 | "error_during_run": False 32 | } 33 | self.events.append(event) 34 | self.start_event(event) 35 | 36 | def start_event(self, event): 37 | self.logger.info(f"Starting event: {event["id"]}") 38 | thread = threading.Thread(target=self._run_event, args=(event,)) 39 | thread.start() 40 | self._main_thread_loop(event) 41 | 42 | def _run_event(self, event): 43 | try: 44 | output = event["function"](**event["kwargs"]) 45 | except Exception as e: 46 | self.logger.error(f"Error in event {event["id"]}: {e} \n{traceback.format_exc()}") 47 | event["error_during_run"] = True 48 | output = None 49 | 50 | self.logger.info(f"Event completed: {event["id"]}") 51 | if output is None: 52 | output = {} 53 | 54 | if not output: 55 | self.logger.warning(f"Event {event["id"]} returned no result") 56 | 57 | event["output_queue"].put(output) 58 | 59 | def _main_thread_loop(self, event): 60 | if not event["output_queue"].empty(): 61 | self.logger.info(f"Processing output for event: {event["id"]}") 62 | output = event["output_queue"].get() 63 | self._process_output(output, event) 64 | return 65 | self.window.after(50, self._main_thread_loop, event) 66 | 67 | def _process_output(self, output, event): 68 | # Assuming result is a dictionary with keys "message_func" and "message_args" 69 | # where both can be None for no message 70 | message = output.get("message") 71 | result = output.get("result") 72 | 73 | # if there was an unhandled error during the event, run the error functions if provided 74 | if event["error_during_run"] and event["error_functions"] and not event["ignore_messages"]: 75 | for error_func in event["error_functions"]: 76 | error_func() 77 | 78 | if message and not event["ignore_messages"]: 79 | message["function"](*message["arguments"]) 80 | 81 | # run the completion functions 82 | for completion_func in event["completion_functions"]: 83 | completion_func() 84 | 85 | # if a completion function with result was provided, run it 86 | # and pass the result of the event to it 87 | # only if there was no error during the event 88 | if event["completion_func_with_result"] and not event["error_during_run"]: 89 | for completion_func in event["completion_func_with_result"]: 90 | completion_func(*result) 91 | 92 | # Remove the event from the list of events 93 | self.events.remove(event) 94 | self.logger.info(f"{event["id"]} event result processed and removed from event list") 95 | -------------------------------------------------------------------------------- /src/gui/libs/CTkMessagebox/messagebox.py: -------------------------------------------------------------------------------- 1 | from CTkMessagebox import CTkMessagebox 2 | 3 | 4 | def show_messagebox(master, title, message, icon, option_1="OK", option_2=None, option_3=None): 5 | output = CTkMessagebox( 6 | master=master, 7 | title=title, 8 | message=message, 9 | icon=icon, 10 | option_1=option_1, 11 | option_2=option_2, 12 | option_3=option_3, 13 | fade_in_duration=75, 14 | width=450, 15 | justify="center", 16 | sound=True, 17 | wraplength=350, 18 | ).get() 19 | return output if output is None else output.lower() 20 | 21 | 22 | def showinfo(master, title, message, option_1="OK"): 23 | return show_messagebox(master=master, title=title, message=message, icon="info", option_1=option_1) 24 | 25 | 26 | def showsuccess(master, title, message): 27 | return show_messagebox(master=master, title=title, message=message, icon="check", option_1="OK") 28 | 29 | 30 | def showwarning(master, title, message): 31 | return show_messagebox(master=master, title=title, message=message, icon="warning", option_1="OK") 32 | 33 | 34 | def showerror(master, title, message): 35 | return show_messagebox(master=master, title=title, message=message, icon="cancel", option_1="OK") 36 | 37 | 38 | def askyesno(master, title, message, icon="question"): 39 | return show_messagebox(master=master, title=title, message=message, icon=icon, option_1="Yes", option_2="No") 40 | 41 | 42 | def askokcancel(master, title, message, icon="question"): 43 | return show_messagebox(master=master, title=title, message=message, icon=icon, option_1="OK", option_2="Cancel") 44 | 45 | 46 | def askretrycancel(master, title, message, icon="question"): 47 | return show_messagebox(master=master, title=title, message=message, icon=icon, option_1="Retry", option_2="Cancel") 48 | 49 | 50 | def askyesnocancel(master, title, message, icon="question"): 51 | return show_messagebox(master=master, title=title, message=message, icon=icon, option_1="Yes", option_2="No", option_3="Cancel") 52 | -------------------------------------------------------------------------------- /src/gui/libs/CTkScrollableDropdown/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Akash Bora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/gui/libs/CTkScrollableDropdown/README.md: -------------------------------------------------------------------------------- 1 | # CTkScrollableDropdown 2 | Replace the old looking tkMenu and add this new scrollable dropdown menu to customtkinter **optionmenu, combobox, entries, buttons** etc... 3 | 4 | ## Features 5 | - Rounded corners 6 | - **Define custom height for the menu** 7 | - Automatic resize 8 | - Transparency effects 9 | - **Autocomplete options** 10 | - Full customisability 11 | - Add images to options 12 | - Automatic bindings added for ctkoptionmenu/ctkcombobox 13 | 14 | ![Platform](https://img.shields.io/powershellgallery/p/Pester?color=blue) 15 | 16 | ![screenshots](https://user-images.githubusercontent.com/89206401/236677843-8d8b76fd-6145-47b1-8f4d-b6a64b08e1ea.png) 17 | 18 | ## Installation 19 | ### [GitHub repo size](https://github.com/Akascape/CTkScrollableDropdown/archive/refs/heads/main.zip) 20 | 21 | **Download the source code, paste the `CTkScrollableDropdown` folder in the directory where your program is present.** 22 | 23 | Note: this type of dropdown was discontinued from ctk because of some tkinter OS-related bugs. But I have tried to fix them by experimenting with different window attributes. 24 | Current Status: 25 | Windows: ✅ 26 | Linux: ✅ 27 | Mac OS: ⚠️ (random crashes can happen, help wanted) 28 | 29 | ## Simple Usage 30 | ```python 31 | CTkScrollableDropdown(attach=widget_name, values=option_list) 32 | ``` 33 | 34 | ## Full Example 35 | ```python 36 | from CTkScrollableDropdown import * 37 | import customtkinter 38 | 39 | root = customtkinter.CTk() 40 | 41 | customtkinter.CTkLabel(root, text="Different Dropdown Styles").pack(pady=5) 42 | 43 | # Some option list 44 | values = ["python","tkinter","customtkinter","widgets", 45 | "options","menu","combobox","dropdown","search"] 46 | 47 | # Attach to OptionMenu 48 | optionmenu = customtkinter.CTkOptionMenu(root, width=240) 49 | optionmenu.pack(fill="x", padx=10, pady=10) 50 | 51 | CTkScrollableDropdown(optionmenu, values=values) 52 | 53 | # Attach to Combobox 54 | combobox = customtkinter.CTkComboBox(root, width=240) 55 | combobox.pack(fill="x", padx=10, pady=10) 56 | 57 | CTkScrollableDropdown(combobox, values=values, justify="left", button_color="transparent") 58 | 59 | # Attach to Entry 60 | customtkinter.CTkLabel(root, text="Live Search Values").pack() 61 | 62 | entry = customtkinter.CTkEntry(root, width=240) 63 | entry.pack(fill="x", padx=10, pady=10) 64 | 65 | CTkScrollableDropdown(entry, values=values, command=lambda e: entry.insert(1, e), 66 | autocomplete=True) # Using autocomplete 67 | 68 | # Attach to Button 69 | button = customtkinter.CTkButton(root, text="choose options", width=240) 70 | button.pack(fill="x", padx=10, pady=10) 71 | 72 | CTkScrollableDropdown(button, values=values, height=270, resize=False, button_height=30, 73 | scrollbar=False, command=lambda e: button.configure(text=e)) 74 | 75 | root.mainloop() 76 | ``` 77 | 78 | ## Arguments 79 | | Parameter | Description | 80 | |-----------| ------------| 81 | | **attach** | parent widget to which the dropdown menu will be attached | 82 | | x | **optional**, change the horizontal offset of the widget manually | 83 | | y | **optional**, change the vertical offset of the widget manually | 84 | | width | **optional**, change the default width of the menu | 85 | | height | **optional**, change the default height of the menu | 86 | | **values** | add the list of options in the dropdown menu | 87 | | image_values | **optional**, add list of images in options | 88 | | **fg_color** | change the fg_color of the scrollable frame | 89 | | button_color | change the fg_color of the buttons/options | 90 | | hover_color | change the hover_color of the buttons/options | 91 | | text_color | change the text_color of the buttons/options | 92 | | button_height | change the height of the buttons if required 93 | | **autocomplete** | add live search options for ctkcombobox or ctkentry widget | 94 | | **alpha** | change the transparency of the whole dropdown widget (range: 0-1) | 95 | | justify | change the anchor of the option text | 96 | | frame_corner_radius | adjust roundness of the frame corners | 97 | | double_click | bind double click for menu popup | 98 | | resize | resize the menu dynamically, default: True | 99 | | frame_border_width | change the border_width of the frame if required | 100 | | frame_border_color | change the border_color of the frame | 101 | | scrollbar | hide the scrollbar if required, default: True | 102 | | command | add the command when option is selected | 103 | | _*Other Parameters_ | _All other parameters for ctkbutton or scrollbar can be passed in dropdownmenu_ | 104 | 105 | ## Methods 106 | - **.insert(value="new_option", ...)** 107 | 108 | Add a new option to the list 109 | - **.configure(values=new_values, height=500, ...)** 110 | 111 | Update some parameters for the dropdown 112 | - **.live_update(string="option")** 113 | 114 | filter options dynamically 115 | - **.destroy_popup()** 116 | 117 | Remove the dropdown completely 118 | - **.popup(x, y)** 119 | 120 | Show popup menu manually 121 | 122 | Note: if you are facing some issues then try using the `CTkScrollableDropdownFrame`. 123 | -------------------------------------------------------------------------------- /src/gui/libs/CTkScrollableDropdown/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CustomTkinter Scrollable Dropdown Menu 3 | Author: Akash Bora 4 | License: MIT 5 | This is a custom dropdown menu for customtkinter. 6 | Homepage: https://github.com/Akascape/CTkScrollableDropdown 7 | """ 8 | 9 | __version__ = '1.1' 10 | 11 | from .ctk_scrollable_dropdown import CTkScrollableDropdown 12 | from .ctk_scrollable_dropdown_frame import CTkScrollableDropdownFrame 13 | -------------------------------------------------------------------------------- /src/gui/windows/folder_selector.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from tkinter import filedialog 4 | 5 | from customtkinter import (CTkButton, CTkCheckBox, CTkEntry, CTkFrame, 6 | CTkScrollableFrame, CTkToplevel) 7 | 8 | 9 | class FolderSelector(CTkToplevel): 10 | def __init__(self, *args, title, predefined_directory=None, allowed_folders=None, show_files=False, **kwargs): 11 | super().__init__(*args, **kwargs) 12 | self.populating = False 13 | self.directory = None 14 | self.predefined_directory = predefined_directory 15 | self.allowed_folders = allowed_folders 16 | self.show_files = show_files 17 | # Central frame 18 | self.title(title) 19 | self.geometry("700x500") 20 | self.central_frame = CTkFrame(self) 21 | self.central_frame.grid(row=0, column=0, sticky='nsew') 22 | 23 | # Configure grid weights 24 | self.grid_rowconfigure(0, weight=1) # Central frame gets all the available vertical space 25 | self.grid_columnconfigure(0, weight=1) # Central frame gets all the available horizontal space 26 | self.central_frame.grid_rowconfigure(1, weight=1) # Scrollable frame gets all the available vertical space 27 | self.central_frame.grid_columnconfigure(0, weight=2) # Entry widget gets 2 parts of the available space 28 | self.central_frame.grid_columnconfigure(1, weight=1) # Browse button gets 1 part of the available space 29 | 30 | # Checkbuttons 31 | self.checkbuttons = [] 32 | 33 | # Entry and Browse button 34 | self.entry = CTkEntry(self.central_frame) 35 | self.entry.grid(row=0, column=0, sticky='ew', padx=(20, 10), pady=10) # Increase padx and pady 36 | self.entry.bind('', lambda e: self.populate_checkbuttons(self.entry.get())) 37 | self.browse_button = CTkButton(self.central_frame, text="Browse", command=self.browse) 38 | self.browse_button.grid(row=0, column=1, padx=10, pady=10) # Increase padx and pady 39 | 40 | # Scrollable frame for Checkbuttons 41 | self.scroll_frame = CTkScrollableFrame(self.central_frame) 42 | self.scroll_frame.grid(row=1, column=0, columnspan=2, sticky='nsew', padx=10, pady=10) # Increase padx and pady 43 | 44 | # Frame for OK and Cancel buttons 45 | self.button_frame = CTkFrame(self.central_frame) 46 | self.button_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=10) # Increase padx and pady 47 | 48 | # OK and Cancel buttons 49 | self.ok_button = CTkButton(self.button_frame, text="OK", command=self.ok) 50 | self.ok_button.pack(side='left', padx=10, pady=10) # Increase padx and pady 51 | self.cancel_button = CTkButton(self.button_frame, text="Cancel", command=self.cancel) 52 | self.cancel_button.pack(side='right', padx=10, pady=10) # Increase padx and pady 53 | 54 | # Disable entry and browse button if predefined_directory is not None 55 | if self.predefined_directory is not None: 56 | self.entry.insert(0, self.predefined_directory) 57 | self.entry.configure(state='disabled') 58 | self.browse_button.configure(state='disabled') 59 | self.populate_checkbuttons(self.predefined_directory) 60 | self.result = None 61 | self.grab_set() 62 | self.protocol("WM_DELETE_WINDOW", self._on_closing) 63 | 64 | def browse(self): 65 | directory = filedialog.askdirectory() 66 | self.entry.delete(0, 'end') 67 | self.entry.insert(0, directory) 68 | self.populate_checkbuttons(directory) 69 | 70 | def populate_checkbuttons(self, directory): 71 | if self.populating or directory == "" or not os.path.isdir(directory) or not os.path.exists(directory): 72 | return 73 | self.directory = None 74 | self.populating = True 75 | # Remove old checkbuttons 76 | for cb in self.checkbuttons: 77 | cb.pack_forget() 78 | self.checkbuttons.clear() 79 | 80 | # Add new checkbuttons 81 | for name in os.listdir(directory): 82 | full_path = os.path.join(directory, name) 83 | if os.path.isdir(full_path) or (self.show_files and os.path.isfile(full_path)): 84 | cb = CTkCheckBox(self.scroll_frame, text=name) 85 | cb.pack(side='top', fill='x', pady=5) 86 | # Disable checkbox if allowed_folders is not None and name is not in allowed_folders 87 | if self.allowed_folders is not None and name not in self.allowed_folders: 88 | cb.configure(state='disabled') 89 | self.checkbuttons.append(cb) 90 | self.directory = directory 91 | self.populating = False 92 | 93 | def ok(self): 94 | self.result = (Path(self.directory), [cb.cget('text') for cb in self.checkbuttons if cb.get()]) 95 | self.grab_release() 96 | self.destroy() 97 | 98 | def cancel(self): 99 | self.result = (None, None) 100 | self.grab_release() 101 | self.destroy() 102 | 103 | def get_input(self): 104 | self.master.wait_window(self) 105 | return self.result 106 | 107 | def _on_closing(self): 108 | self.result = (None, None) 109 | self.grab_release() 110 | self.destroy() 111 | -------------------------------------------------------------------------------- /src/gui/windows/saves_browser.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from pathlib import Path 3 | from urllib.parse import unquote 4 | 5 | import customtkinter 6 | 7 | from core.config import constants 8 | from core.network.web import download_file_with_progress 9 | from gui.handlers.progress.progress_handler import ProgressHandler 10 | from gui.libs.CTkMessagebox import messagebox 11 | 12 | 13 | class SavesBrowser(customtkinter.CTkToplevel): 14 | def __init__(self, master, title, saves_list, event_manager, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | self.title(title) 17 | self.saves = saves_list 18 | self.downloading_save = False 19 | self.event_manager = event_manager 20 | self.progress_handler = ProgressHandler(self, widget="window") 21 | self.lift() # lift window on top 22 | self.protocol("WM_DELETE_WINDOW", self.on_closing) 23 | self.after(30, self.build_frame) # create widgets with slight delay, to avoid white flickering of background 24 | self.grab_set() # make other windows not clickable 25 | 26 | def build_frame(self): 27 | self.resizable(False, True) 28 | self.minsize(415, 400) 29 | self.geometry("415x600") 30 | # Create a label for the title 31 | title_label = customtkinter.CTkLabel(self, text="Download Saves", font=customtkinter.CTkFont("Arial", 20), anchor="center") 32 | title_label.grid(row=0, column=0, sticky="nsew") 33 | 34 | # Create a label for the paragraph 35 | paragraph_label = customtkinter.CTkLabel(self, font=customtkinter.CTkFont("Arial", 15), anchor="center", text=textwrap.fill("Once the download is complete, the save file will be placed on your desktop. To install a save, right click then game in the emulator game list, "'open save folder'" and extract the downloaded archive there. Be sure to backup your current save file if you want to use it later!", 60)) 36 | paragraph_label.grid(row=1, column=0, pady=20, sticky="nsew") 37 | 38 | # Create a vertically scrollable frame 39 | scrollable_frame = customtkinter.CTkScrollableFrame(self) 40 | scrollable_frame.grid(row=2, column=0, sticky="nsew") 41 | 42 | # make buttons expand to fill the entire width of the frame 43 | scrollable_frame.grid_columnconfigure(0, weight=1) 44 | 45 | for i, save in enumerate(self.saves): 46 | filename = unquote(save).split('/')[-1].split(".zip")[-2] 47 | 48 | # Create a button for the save 49 | save_button_text = textwrap.fill(filename, width=38) # Insert newlines into the filename 50 | save_button = customtkinter.CTkButton(scrollable_frame, text=save_button_text, font=customtkinter.CTkFont("Arial", 20), command=lambda save=save: self.download_save_button_event(save)) 51 | save_button.grid(row=i, column=0, padx=(2, 2), pady=5, sticky="ew") 52 | save_button.update_idletasks() 53 | # Configure the grid to allocate all extra space to the scrollable frame 54 | self.grid_rowconfigure(2, weight=1) 55 | self.grid_columnconfigure(0, weight=1) 56 | 57 | def download_save_button_event(self, save): 58 | if self.downloading_save: 59 | return 60 | self.downloading_save = True 61 | self.event_manager.add_event( 62 | event_id="download_save", 63 | func=self.download_save, 64 | kwargs={"save": save}, 65 | error_functions=[lambda: messagebox.showerror(self, "Save Download", "An unexpected error occurred while attempting to download this save"), lambda: setattr(self, "downloading_save", False)], 66 | ) 67 | 68 | def download_save(self, save): 69 | save_download_url = constants.GitHub.RAW_URL.value.format( 70 | owner=constants.Switch.SAVES_GH_REPO_OWNER.value, 71 | repo=constants.Switch.SAVES_GH_REPO_NAME.value, 72 | branch="main", 73 | path=f"{constants.Switch.SAVES_GH_REPO_PATH.value}/{save}", 74 | ) 75 | self.progress_handler.start_operation(title=save, total_units=0, units="MiB", status="Downloading...") 76 | download_result = download_file_with_progress( 77 | download_url=save_download_url, 78 | download_path=Path.home() / "Desktop" / save, 79 | progress_handler=self.progress_handler, 80 | ) 81 | 82 | self.downloading_save = False 83 | if not download_result["status"]: 84 | return { 85 | "message": { 86 | "function": messagebox.showerror, 87 | "arguments": (self, "Save Download", f"An error occurred while attempting to download this save\n\n{download_result['message']}"), 88 | } 89 | } 90 | 91 | return { 92 | "message": { 93 | "function": messagebox.showsuccess, 94 | "arguments": (self, "Save Download", "The savefile was successfully downloaded to your desktop"), 95 | } 96 | } 97 | 98 | def on_closing(self): 99 | self.grab_release() 100 | self.destroy() # destroy window 101 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import customtkinter 4 | 5 | from core.config.assets import Assets 6 | from core.config.cache import Cache 7 | from core.config.paths import Paths 8 | from core.config.settings import Settings 9 | from core.config.versions import Versions 10 | from core.logging.logger import Logger 11 | from gui.emuhaven import EmuHaven 12 | 13 | logger = Logger(__name__).get_logger() 14 | 15 | if __name__ == "__main__": 16 | logger.info("Starting the application with arguments: %s", sys.argv[1:]) 17 | 18 | paths = Paths() 19 | settings = Settings(paths) 20 | versions = Versions(paths) 21 | cache = Cache(paths) 22 | 23 | args = sys.argv[1:] 24 | if args: 25 | logger.info("Starting the application in CLI mode") 26 | 27 | else: 28 | try: 29 | # attempt to load assets 30 | assets = Assets(paths) 31 | assets.define_assets() 32 | except FileNotFoundError as e: 33 | # if assets are not found, log error and exit 34 | logger.error("Failed to load assets: %s", e) 35 | sys.exit(1) 36 | 37 | # set the appearance mode and colour theme 38 | theme = settings.colour_theme_path 39 | dark_mode = settings.dark_mode 40 | logger.info("Dark: %s", dark_mode) 41 | logger.info("Theme: %s", theme) 42 | customtkinter.set_appearance_mode("dark" if dark_mode else "light") 43 | customtkinter.set_default_color_theme(str(theme)) 44 | 45 | # instantiate the application 46 | logger.info("Building the application") 47 | app = EmuHaven( 48 | paths=paths, 49 | settings=settings, 50 | versions=versions, 51 | cache=cache, 52 | assets=assets 53 | ) 54 | 55 | # call mainloop to start the Tkinter application 56 | logger.info("Application started") 57 | app.mainloop() 58 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/blog/2024-09-18-v0.14.0.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | authors: viren 3 | --- 4 | -------------------------------------------------------------------------------- /website/blog/authors.yml: -------------------------------------------------------------------------------- 1 | viren: 2 | name: Viren070 3 | title: EmuHaven Creator 4 | page: True 5 | socials: 6 | github: Viren070 7 | -------------------------------------------------------------------------------- /website/blog/tags.yml: -------------------------------------------------------------------------------- 1 | facebook: 2 | label: Facebook 3 | permalink: /facebook 4 | description: Facebook tag description 5 | 6 | hello: 7 | label: Hello 8 | permalink: /hello 9 | description: Hello tag description 10 | 11 | docusaurus: 12 | label: Docusaurus 13 | permalink: /docusaurus 14 | description: Docusaurus tag description 15 | 16 | hola: 17 | label: Hola 18 | permalink: /hola 19 | description: Hola tag description 20 | -------------------------------------------------------------------------------- /website/docs/index.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/docs/index.mdx -------------------------------------------------------------------------------- /website/docs/intro.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/docs/intro.md -------------------------------------------------------------------------------- /website/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import {themes as prismThemes} from 'prism-react-renderer'; 2 | import type {Config} from '@docusaurus/types'; 3 | import type * as Preset from '@docusaurus/preset-classic'; 4 | 5 | const config: Config = { 6 | title: 'EmuHaven', 7 | tagline: 'Dinosaurs are cool', 8 | favicon: 'img/favicon.ico', 9 | 10 | // Set the production url of your site here 11 | url: 'https://emuhaven.viren070.me', 12 | // Set the // pathname under which your site is served 13 | // For GitHub pages deployment, it is often '//' 14 | baseUrl: '/', 15 | 16 | // GitHub pages deployment config. 17 | // If you aren't using GitHub pages, you don't need these. 18 | organizationName: 'Viren070', // Usually your GitHub org/user name. 19 | projectName: 'EmuHaven', // Usually your repo name. 20 | 21 | trailingSlash: false, 22 | onBrokenLinks: 'throw', 23 | onBrokenMarkdownLinks: 'warn', 24 | 25 | // Even if you don't use internationalization, you can use this field to set 26 | // useful metadata like html lang. For example, if your site is Chinese, you 27 | // may want to replace "en" with "zh-Hans". 28 | i18n: { 29 | defaultLocale: 'en', 30 | locales: ['en'], 31 | }, 32 | 33 | presets: [ 34 | [ 35 | 'classic', 36 | { 37 | docs: { 38 | sidebarPath: './sidebars.ts', 39 | // Please change this to your repo. 40 | // Remove this to remove the "edit this page" links. 41 | editUrl: 42 | 'https://github.com/Viren070/EmuHaven/tree/main/website/', 43 | }, 44 | blog: { 45 | showReadingTime: true, 46 | feedOptions: { 47 | type: ['rss', 'atom'], 48 | xslt: true, 49 | }, 50 | // Please change this to your repo. 51 | // Remove this to remove the "edit this page" links. 52 | editUrl: 53 | 'https://github.com/Viren070/EmuHaven/tree/main/website/', 54 | // Useful options to enforce blogging best practices 55 | onInlineTags: 'warn', 56 | onInlineAuthors: 'warn', 57 | onUntruncatedBlogPosts: 'warn', 58 | }, 59 | theme: { 60 | customCss: './src/css/custom.css', 61 | }, 62 | } satisfies Preset.Options, 63 | ], 64 | ], 65 | 66 | themeConfig: { 67 | // Replace with your project's social card 68 | image: 'img/docusaurus-social-card.jpg', 69 | navbar: { 70 | title: 'EmuHaven', 71 | logo: { 72 | alt: 'My Site Logo', 73 | src: 'img/logo.svg', 74 | }, 75 | items: [ 76 | { 77 | type: 'docSidebar', 78 | sidebarId: 'tutorialSidebar', 79 | position: 'left', 80 | label: 'Docs', 81 | }, 82 | {to: '/blog', label: 'Blog', position: 'left'}, 83 | { 84 | href: 'https://github.com/Viren070/EmuHaven', 85 | label: 'GitHub', 86 | position: 'right', 87 | }, 88 | ], 89 | }, 90 | footer: { 91 | style: 'dark', 92 | links: [ 93 | { 94 | title: 'Docs', 95 | items: [ 96 | { 97 | label: 'Introduction', 98 | to: '/docs/intro', 99 | }, 100 | ], 101 | }, 102 | { 103 | title: 'Community', 104 | items: [ 105 | { 106 | label: 'Discord', 107 | href: 'https://discord.viren070.me/', 108 | }, 109 | ], 110 | }, 111 | { 112 | title: 'More', 113 | items: [ 114 | { 115 | label: 'Blog', 116 | to: '/blog', 117 | }, 118 | { 119 | label: 'GitHub', 120 | href: 'https://github.com/Viren070/EmuHaven', 121 | }, 122 | ], 123 | }, 124 | ], 125 | copyright: `Copyright © ${new Date().getFullYear()} EmuHaven. Website built with Docusaurus.`, 126 | }, 127 | prism: { 128 | theme: prismThemes.github, 129 | darkTheme: prismThemes.dracula, 130 | }, 131 | } satisfies Preset.ThemeConfig, 132 | }; 133 | 134 | export default config; 135 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.5.2", 19 | "@docusaurus/preset-classic": "3.5.2", 20 | "@mdx-js/react": "3.1.0", 21 | "clsx": "2.1.1", 22 | "prism-react-renderer": "2.4.0", 23 | "react": "18.3.1", 24 | "react-dom": "18.3.1" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "3.5.2", 28 | "@docusaurus/tsconfig": "3.5.2", 29 | "@docusaurus/types": "3.5.2", 30 | "typescript": "5.6.3" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.5%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 3 chrome version", 40 | "last 3 firefox version", 41 | "last 5 safari version" 42 | ] 43 | }, 44 | "engines": { 45 | "node": ">=18.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /website/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | tutorialSidebar: [ 15 | 'index', 16 | 'intro', 17 | ], 18 | }; 19 | 20 | export default sidebars; 21 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Heading from '@theme/Heading'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Easy to Use', 14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 15 | description: ( 16 | <> 17 | Docusaurus was designed from the ground up to be easily installed and 18 | used to get your website up and running quickly. 19 | 20 | ), 21 | }, 22 | { 23 | title: 'Focus on What Matters', 24 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 25 | description: ( 26 | <> 27 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 28 | ahead and move your docs into the docs directory. 29 | 30 | ), 31 | }, 32 | { 33 | title: 'Powered by React', 34 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 35 | description: ( 36 | <> 37 | Extend or customize your website layout by reusing React. Docusaurus can 38 | be extended while reusing the same header and footer. 39 | 40 | ), 41 | }, 42 | ]; 43 | 44 | function Feature({title, Svg, description}: FeatureItem) { 45 | return ( 46 |
47 |
48 | 49 |
50 |
51 | {title} 52 |

{description}

53 |
54 |
55 | ); 56 | } 57 | 58 | export default function HomepageFeatures(): JSX.Element { 59 | return ( 60 |
61 |
62 |
63 | {FeatureList.map((props, idx) => ( 64 | 65 | ))} 66 |
67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Link from '@docusaurus/Link'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import Layout from '@theme/Layout'; 5 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 6 | import Heading from '@theme/Heading'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | 16 | {siteConfig.title} 17 | 18 |

{siteConfig.tagline}

19 |
20 | 23 | Docusaurus Tutorial - 5min ⏱️ 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | 31 | export default function Home(): JSX.Element { 32 | const {siteConfig} = useDocusaurusContext(); 33 | return ( 34 | 37 | 38 |
39 | 40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /website/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /website/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/static/img/docusaurus.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viren070/emuhaven/441ba6cdbd97526a2eae7895703523ad110d6af0/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | --------------------------------------------------------------------------------