├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── assets ├── lottie.json └── pass.png ├── buildozer.spec ├── kivymd ├── __init__.py ├── app.py ├── color_definitions.py ├── effects │ ├── __init__.py │ ├── roulettescroll │ │ ├── LICENSE │ │ ├── README.md │ │ └── __init__.py │ └── stiffscroll │ │ ├── LICENSE │ │ ├── README.md │ │ └── __init__.py ├── factory_registers.py ├── font_definitions.py ├── fonts │ ├── DejavuSans.ttf │ ├── DejavuSansBold.ttf │ ├── Marvel-Bold.ttf │ ├── Poppins-Bold.ttf │ ├── Poppins-Regular.ttf │ ├── Roboto-Black.ttf │ ├── Roboto-BlackItalic.ttf │ ├── Roboto-Bold.ttf │ ├── Roboto-BoldItalic.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-LightItalic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-MediumItalic.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-Thin.ttf │ ├── Roboto-ThinItalic.ttf │ └── materialdesignicons-webfont.ttf ├── icon_definitions.py ├── images │ ├── folder.png │ ├── kivy-logo-white-512.png │ ├── kivymd_512.png │ ├── kivymd_alpha.png │ ├── kivymd_logo.png │ ├── quad_shadow-0.png │ ├── quad_shadow-1.png │ ├── quad_shadow-2.png │ ├── quad_shadow.atlas │ ├── rec_shadow-0.png │ ├── rec_shadow-1.png │ ├── rec_shadow.atlas │ ├── rec_st_shadow-0.png │ ├── rec_st_shadow-1.png │ ├── rec_st_shadow-2.png │ ├── rec_st_shadow.atlas │ ├── round_shadow-0.png │ ├── round_shadow-1.png │ ├── round_shadow-2.png │ ├── round_shadow.atlas │ └── transparent.png ├── material_resources.py ├── stiffscroll │ ├── LICENSE │ ├── README.md │ └── __init__.py ├── tests │ ├── pyinstaller │ │ └── test_pyinstaller_packaging.py │ ├── test_app.py │ ├── test_font_definitions.py │ └── test_icon_definitions.py ├── theming.py ├── theming_dynamic_text.py ├── toast │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── androidtoast │ │ ├── __init__.py │ │ └── androidtoast.py │ └── kivytoast │ │ ├── __init__.py │ │ └── kivytoast.py ├── tools │ ├── __init__.py │ ├── packaging │ │ ├── __init__.py │ │ └── pyinstaller │ │ │ ├── __init__.py │ │ │ └── hook-kivymd.py │ ├── release │ │ ├── __init__.py │ │ ├── argument_parser.py │ │ ├── git_commands.py │ │ ├── make_release.py │ │ └── update_icons.py │ └── update_icons.py ├── uix │ ├── __init__.py │ ├── backdrop.py │ ├── banner.py │ ├── behaviors │ │ ├── __init__.py │ │ ├── backgroundcolor_behavior.py │ │ ├── backgroundcolorbehavior.py │ │ ├── elevation.py │ │ ├── focus_behavior.py │ │ ├── hover_behavior.py │ │ ├── magic_behavior.py │ │ ├── ripple_behavior.py │ │ ├── ripplebehavior.py │ │ ├── toggle_behavior.py │ │ └── touch_behavior.py │ ├── bottomnavigation.py │ ├── bottomsheet.py │ ├── boxlayout.py │ ├── button.py │ ├── card.py │ ├── carousel.py │ ├── chip.py │ ├── circularlayout.py │ ├── context_menu.py │ ├── datatables.py │ ├── dialog.py │ ├── dropdownitem.py │ ├── expansionpanel.py │ ├── filemanager.py │ ├── floatlayout.py │ ├── gridlayout.py │ ├── imagelist.py │ ├── label.py │ ├── list.py │ ├── menu.py │ ├── navigationdrawer.py │ ├── navigationrail.py │ ├── picker.py │ ├── progressbar.py │ ├── progressloader.py │ ├── refreshlayout.py │ ├── relativelayout.py │ ├── screen.py │ ├── selection.py │ ├── selectioncontrol.py │ ├── slider.py │ ├── snackbar.py │ ├── spinner.py │ ├── stacklayout.py │ ├── swiper.py │ ├── tab.py │ ├── taptargetview.py │ ├── textfield.py │ ├── toolbar.py │ ├── tooltip.py │ └── useranimationcard.py ├── utils │ ├── __init__.py │ ├── asynckivy.py │ ├── cropimage.py │ ├── fitimage.py │ ├── fpsmonitor.py │ └── hot_reload_viewer.py └── vendor │ ├── __init__.py │ ├── circleLayout │ ├── LICENSE │ ├── README.md │ └── __init__.py │ └── circularTimePicker │ ├── LICENSE │ ├── README.md │ └── __init__.py ├── libs ├── encryption.py ├── firebase.py ├── modules │ ├── AndroidAPI.py │ ├── CardTextField.py │ ├── List.py │ ├── Toolbar.py │ ├── dialogs.py │ ├── picker.py │ └── spinners.py ├── save_config.py ├── screens │ ├── HomeScreen │ │ ├── HomeScreen.kv │ │ └── HomeScreen.py │ ├── LoginScreen │ │ ├── LoginScreen.kv │ │ └── LoginScreen.py │ ├── SettingsScreen │ │ ├── SettingsScreen.kv │ │ └── SettingsScreen.py │ ├── SignupScreen │ │ ├── SignupScreen.kv │ │ └── SignupScreen.py │ ├── classes.py │ └── root.py └── utils.py ├── main.py ├── requirements.txt └── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png └── 6.png /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build APK 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | 7 | jobs: 8 | build-android: 9 | name: Build for Android 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | # Setting environment variables for app. 15 | - name: Create env file 16 | run: | 17 | cd ${{ github.workspace }} 18 | echo "import os 19 | os.environ['WEB_API_KEY'] = '${{ secrets.WEB_API_KEY }}' 20 | os.environ['DATABASE_URL'] = '${{ vars.DATABASE_URL }}'" > libs/firebase_config.py 21 | cat libs/firebase_config.py 22 | - name: Build with Buildozer 23 | run: | 24 | pip3 install --user --upgrade buildozer 25 | sudo apt update 26 | sudo apt install -y git zip unzip openjdk-17-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev 27 | pip3 install --user --upgrade Cython==0.29.33 virtualenv 28 | export PATH=$PATH:~/.local/bin/ 29 | export APP_ANDROID_ACCEPT_SDK_LICENSE=1 30 | export BUILDOZER_WARN_ON_ROOT=0 31 | buildozer android debug 32 | - name: Upload artifacts 33 | uses: actions/upload-artifact@v2 34 | with: 35 | name: package 36 | path: bin/*.apk 37 | 38 | - name: Apk Release 39 | uses: softprops/action-gh-release@v1 40 | if: startsWith(github.ref, 'refs/tags/') 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | files: bin/*.apk 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .buildozer 3 | test.py 4 | OldPasslock 5 | PassLOCK_old 6 | OtherStuff 7 | kivymd1 8 | kivymd2 9 | kivymd_orginal 10 | .idea 11 | screenshot_original 12 | __pycache__ 13 | .vscode 14 | data 15 | test.txt 16 | libs/firebase_config.py 17 | backup_design.kv 18 | myapp.profile 19 | .env 20 | api_key.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Passlock for android 7 |

8 | 9 |

10 | 11 | See video demo 12 | 13 |

14 | 15 | 16 | A Password Manager for android. 17 | 18 | Also Check Out Passlock for desktop 💻 [here.](https://github.com/AM-ash-OR-AM-I/PasslockDesktop) 19 | 20 | ## Features ✨ 21 | 22 | * Backup and sync passwords across devices 💻📱. 23 | * Encrypted passwords using AES 128bit for maximum security. 24 | * Mimics Material v3 Monet engine with 🌙 Dark Mode, to use different 🎨 color themes. (Self-made) 25 | * Make strong passwords 🔑 through built in password generator. 26 | * Advanced 🔍 finding algorithm to search for passwords easily. 27 | 28 | ## Releases 📥️ 29 | 30 | #### Check latest apk links for [📱 Android here.](https://github.com/AM-ash-OR-AM-I/Passlock/releases) 31 | 32 | ## Build 📦️ 33 | 34 | * Get `WEB_API_KEY` from firebase project and add it to github secrets. 35 | * Add `DATABASE_URL` to repository variables. 36 | * Now you are all set build app 🎉, make some changes push it then tag a release 🚀 37 | * e.g. `git tag v0.0.1 && git push --tags` 38 | * Workflow will be triggered and will automatically create a release for apk. 39 | 40 | ## Screenshots 📱 41 | 42 | ![1](./screenshots/1.png) | ![2](./screenshots/2.png) | ![3](./screenshots/3.png) | 43 | ------------------------- | ------------------------- | ------------------------- | 44 | ![4](./screenshots/4.png) | ![2](./screenshots/5.png) | ![3](./screenshots/6.png) | 45 | -------------------------------------------------------------------------------- /assets/pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/assets/pass.png -------------------------------------------------------------------------------- /kivymd/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | KivyMD 3 | ====== 4 | 5 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/previous.png 6 | 7 | Is a collection of Material Design compliant widgets for use with, 8 | `Kivy cross-platform graphical framework `_ 9 | a framework for cross-platform, touch-enabled graphical applications. 10 | The project's goal is to approximate Google's `Material Design spec 11 | `_ as close as possible without 12 | sacrificing ease of use or application performance. 13 | 14 | This library is a fork of the `KivyMD project 15 | `_ the author of which stopped supporting 16 | this project three years ago. We found the strength and brought this project 17 | to a new level. Currently we're in **beta** status, so things are changing 18 | all the time and we cannot promise any kind of API stability. 19 | However it is safe to vendor now and make use of what's currently available. 20 | 21 | Join the project! Just fork the project, branch out and submit a pull request 22 | when your patch is ready. If any changes are necessary, we'll guide you 23 | through the steps that need to be done via PR comments or access to your for 24 | may be requested to outright submit them. If you wish to become a project 25 | developer (permission to create branches on the project without forking for 26 | easier collaboration), have at least one PR approved and ask for it. 27 | If you contribute regularly to the project the role may be offered to you 28 | without asking too. 29 | """ 30 | 31 | import os 32 | 33 | import kivy 34 | from kivy.logger import Logger 35 | 36 | __version__ = "0.104.2.dev0" 37 | """KivyMD version.""" 38 | 39 | release = False 40 | kivy.require("2.0.0") 41 | 42 | try: 43 | from kivymd._version import __date__, __hash__, __short_hash__ 44 | except ImportError: 45 | __hash__ = __short_hash__ = __date__ = "" 46 | 47 | path = os.path.dirname(__file__) 48 | """Path to KivyMD package directory.""" 49 | 50 | fonts_path = os.path.join(path, f"fonts{os.sep}") 51 | """Path to fonts directory.""" 52 | 53 | images_path = os.path.join(path, f"images{os.sep}") 54 | """Path to images directory.""" 55 | 56 | _log_message = ( 57 | "KivyMD:" 58 | + (" Release" if release else "") 59 | + f" {__version__}" 60 | + (f", git-{__short_hash__}" if __short_hash__ else "") 61 | + (f", {__date__}" if __date__ else "") 62 | + f' (installed at "{__file__}")' 63 | ) 64 | Logger.info(_log_message) 65 | 66 | import kivymd.factory_registers # NOQA 67 | import kivymd.font_definitions # NOQA 68 | from kivymd.tools.packaging.pyinstaller import hooks_path # NOQA 69 | -------------------------------------------------------------------------------- /kivymd/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Themes/Material App 3 | =================== 4 | 5 | This module contains :class:`MDApp` class that is inherited from 6 | :class:`~kivy.app.App`. :class:`MDApp` has some properties needed for ``KivyMD`` 7 | library (like :attr:`~MDApp.theme_cls`). 8 | 9 | You can turn on the monitor displaying the current ``FPS`` value in your application: 10 | 11 | .. code-block:: python 12 | 13 | KV = ''' 14 | Screen: 15 | 16 | MDLabel: 17 | text: "Hello, World!" 18 | halign: "center" 19 | ''' 20 | 21 | from kivy.lang import Builder 22 | 23 | from kivymd.app import MDApp 24 | 25 | 26 | class MainApp(MDApp): 27 | def build(self): 28 | return Builder.load_string(KV) 29 | 30 | def on_start(self): 31 | self.fps_monitor_start() 32 | 33 | 34 | MainApp().run() 35 | 36 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor.png 37 | :width: 350 px 38 | :align: center 39 | 40 | """ 41 | 42 | __all__ = ("MDApp",) 43 | 44 | from kivy.app import App 45 | from kivy.properties import ObjectProperty 46 | 47 | from kivymd.theming import ThemeManager 48 | 49 | 50 | class FpsMonitoring: 51 | """Adds a monitor to display the current FPS in the toolbar.""" 52 | 53 | def fps_monitor_start(self): 54 | from kivy.core.window import Window 55 | 56 | from kivymd.utils.fpsmonitor import FpsMonitor 57 | 58 | monitor = FpsMonitor() 59 | monitor.start() 60 | Window.add_widget(monitor) 61 | 62 | 63 | class MDApp(App, FpsMonitoring): 64 | theme_cls = ObjectProperty() 65 | """ 66 | Instance of :class:`~ThemeManager` class. 67 | 68 | .. Warning:: The :attr:`~theme_cls` attribute is already available 69 | in a class that is inherited from the :class:`~MDApp` class. 70 | The following code will result in an error! 71 | 72 | .. code-block:: python 73 | 74 | class MainApp(MDApp): 75 | theme_cls = ThemeManager() 76 | theme_cls.primary_palette = "Teal" 77 | 78 | .. Note:: Correctly do as shown below! 79 | 80 | .. code-block:: python 81 | 82 | class MainApp(MDApp): 83 | def build(self): 84 | self.theme_cls.primary_palette = "Teal" 85 | 86 | :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`. 87 | """ 88 | 89 | def __init__(self, **kwargs): 90 | super().__init__(**kwargs) 91 | self.theme_cls = ThemeManager() 92 | -------------------------------------------------------------------------------- /kivymd/effects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/effects/__init__.py -------------------------------------------------------------------------------- /kivymd/effects/roulettescroll/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2021 Kivy Team and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /kivymd/effects/roulettescroll/README.md: -------------------------------------------------------------------------------- 1 | RouletteScrollEffect 2 | =================== 3 | 4 | This is a subclass of `kivy.effects.ScrollEffect` that simulates the 5 | motion of a roulette, or a notched wheel (think Wheel of Fortune). It is 6 | primarily designed for emulating the effect of the iOS and android date pickers. 7 | 8 | Usage 9 | ----- 10 | 11 | Here's an example of using `RouletteScrollEffect` for a `kivy.uix.scrollview.ScrollView`: 12 | 13 | ```python 14 | from kivy.uix.gridlayout import GridLayout 15 | from kivy.uix.button import Button 16 | from kivy.uix.scrollview import ScrollView 17 | 18 | # Preparing a `GridLayout` inside a `ScrollView`. 19 | layout = GridLayout(cols=1, padding=10, size_hint=(None, None), width=500) 20 | layout.bind(minimum_height=layout.setter('height')) 21 | 22 | for i in range(30): 23 | btn = Button(text=str(i), size=(480, 40), size_hint=(None, None)) 24 | layout.add_widget(btn) 25 | 26 | root = ScrollView( 27 | size_hint=(None, None), 28 | size=(500, 320), 29 | pos_hint={'center_x': .5, 'center_y': .5}, 30 | do_scroll_x=False, 31 | ) 32 | root.add_widget(layout) 33 | 34 | # Preparation complete. Now add the new scroll effect. 35 | root.effect_y = RouletteScrollEffect(anchor=20, interval=40) 36 | runTouchApp(root) 37 | ``` 38 | 39 | Here the `ScrollView` scrolls through a series of buttons with height `40`. We then attached a `RouletteScrollEffect` with interval 40, 40 | corresponding to the button heights. This allows the scrolling to stop at 41 | the same offset no matter where it stops. The `RouletteScrollEffect.anchor` 42 | adjusts this offset. 43 | 44 | Customizations 45 | -------------- 46 | 47 | Other settings that can be played with include: 48 | 49 | - `RouletteScrollEffect.pull_duration` 50 | - `RouletteScrollEffect.coasting_alpha` 51 | - `RouletteScrollEffect.pull_back_velocity` 52 | - `RouletteScrollEffect.terminal_velocity` 53 | 54 | See their module documentations for details. 55 | 56 | `RouletteScrollEffect` has one event ``on_coasted_to_stop`` that 57 | is fired when the roulette stops, "making a selection". It can be listened to 58 | for handling or cleaning up choice making. -------------------------------------------------------------------------------- /kivymd/effects/stiffscroll/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 LogicalDash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /kivymd/effects/stiffscroll/README.md: -------------------------------------------------------------------------------- 1 | stiffscroll 2 | =========== 3 | 4 | A ScrollEffect for use with a Kivy ScrollView. It makes scrolling more 5 | laborious as you reach the edge of the scrollable area. 6 | 7 | A ScrollView constructed with StiffScrollEffect, 8 | eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to 9 | scroll as you get nearer to its edges. You can scroll all the way to 10 | the edge if you want to, but it will take more finger-movement than 11 | usual. 12 | 13 | Unlike DampedScrollEffect, it is impossible to overscroll with 14 | StiffScrollEffect. That means you cannot push the contents of the 15 | ScrollView far enough to see what's beneath them. This is appropriate 16 | if the ScrollView contains, eg., a background image, like a desktop 17 | wallpaper. Overscrolling may give the impression that there is some 18 | reason to overscroll, even if just to take a peek beneath, and that 19 | impression may be misleading. 20 | 21 | StiffScrollEffect was written by Zachary Spector. His other stuff is at: 22 | https://github.com/LogicalDash/ 23 | He can be reached, and possibly hired, at: 24 | zacharyspector@gmail.com -------------------------------------------------------------------------------- /kivymd/factory_registers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Register KivyMD widgets to use without import 3 | """ 4 | 5 | from kivy.factory import Factory 6 | 7 | r = Factory.register 8 | r("MDNavigationRail", module="kivymd.uix.navigationrail") 9 | r("MDSwiper", module="kivymd.uix.swiper") 10 | r("MDCarousel", module="kivymd.uix.carousel") 11 | r("MDFloatLayout", module="kivymd.uix.floatlayout") 12 | r("MDScreen", module="kivymd.uix.screen") 13 | r("MDBoxLayout", module="kivymd.uix.boxlayout") 14 | r("MDRelativeLayout", module="kivymd.uix.relativelayout") 15 | r("MDGridLayout", module="kivymd.uix.gridlayout") 16 | r("MDStackLayout", module="kivymd.uix.stacklayout") 17 | r("MDExpansionPanel", module="kivymd.uix.expansionpanel") 18 | r("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") 19 | r("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") 20 | r("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") 21 | r("FitImage", module="kivymd.utils.fitimage") 22 | r("MDBackdrop", module="kivymd.uix.backdrop") 23 | r("MDBanner", module="kivymd.uix.banner") 24 | r("MDTooltip", module="kivymd.uix.tooltip") 25 | r("MDBottomNavigation", module="kivymd.uix.bottomnavigation") 26 | r("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") 27 | r("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior") 28 | r("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button") 29 | r("MDIconButton", module="kivymd.uix.button") 30 | r("MDRoundImageButton", module="kivymd.uix.button") 31 | r("MDFlatButton", module="kivymd.uix.button") 32 | r("MDRaisedButton", module="kivymd.uix.button") 33 | r("MDFloatingActionButton", module="kivymd.uix.button") 34 | r("MDRectangleFlatButton", module="kivymd.uix.button") 35 | r("MDTextButton", module="kivymd.uix.button") 36 | r("MDCustomRoundIconButton", module="kivymd.uix.button") 37 | r("MDRoundFlatButton", module="kivymd.uix.button") 38 | r("MDFillRoundFlatButton", module="kivymd.uix.button") 39 | r("MDRectangleFlatIconButton", module="kivymd.uix.button") 40 | r("MDRoundFlatIconButton", module="kivymd.uix.button") 41 | r("MDFillRoundFlatIconButton", module="kivymd.uix.button") 42 | r("MDCard", module="kivymd.uix.card") 43 | r("MDSeparator", module="kivymd.uix.card") 44 | r("MDSelectionList", module="kivymd.uix.selection") 45 | r("MDChip", module="kivymd.uix.chip") 46 | r("MDChooseChip", module="kivymd.uix.chip") 47 | r("SmartTile", module="kivymd.uix.imagelist") 48 | r("SmartTileWithLabel", module="kivymd.uix.imagelist") 49 | r("SmartTileWithStar", module="kivymd.uix.imagelist") 50 | r("MDLabel", module="kivymd.uix.label") 51 | r("MDIcon", module="kivymd.uix.label") 52 | r("MDList", module="kivymd.uix.list") 53 | r("ILeftBody", module="kivymd.uix.list") 54 | r("ILeftBodyTouch", module="kivymd.uix.list") 55 | r("IRightBody", module="kivymd.uix.list") 56 | r("IRightBodyTouch", module="kivymd.uix.list") 57 | r("ContainerSupport", module="kivymd.uix.list") 58 | r("OneLineListItem", module="kivymd.uix.list") 59 | r("TwoLineListItem", module="kivymd.uix.list") 60 | r("ThreeLineListItem", module="kivymd.uix.list") 61 | r("OneLineAvatarListItem", module="kivymd.uix.list") 62 | r("TwoLineAvatarListItem", module="kivymd.uix.list") 63 | r("ThreeLineAvatarListItem", module="kivymd.uix.list") 64 | r("OneLineIconListItem", module="kivymd.uix.list") 65 | r("TwoLineIconListItem", module="kivymd.uix.list") 66 | r("ThreeLineIconListItem", module="kivymd.uix.list") 67 | r("OneLineRightIconListItem", module="kivymd.uix.list") 68 | r("TwoLineRightIconListItem", module="kivymd.uix.list") 69 | r("ThreeLineRightIconListItem", module="kivymd.uix.list") 70 | r("OneLineAvatarIconListItem", module="kivymd.uix.list") 71 | r("TwoLineAvatarIconListItem", module="kivymd.uix.list") 72 | r("ThreeLineAvatarIconListItem", module="kivymd.uix.list") 73 | r("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") 74 | r("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior") 75 | r("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior") 76 | r("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") 77 | r("MDNavigationLayout", module="kivymd.uix.navigationdrawer") 78 | r("MDProgressBar", module="kivymd.uix.progressbar") 79 | r("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") 80 | r("MDCheckbox", module="kivymd.uix.selectioncontrol") 81 | r("MDSwitch", module="kivymd.uix.selectioncontrol") 82 | r("MDSlider", module="kivymd.uix.slider") 83 | r("MDSpinner", module="kivymd.uix.spinner") 84 | r("MDTabs", module="kivymd.uix.tab") 85 | r("MDTextField", module="kivymd.uix.textfield") 86 | r("MDTextFieldRound", module="kivymd.uix.textfield") 87 | r("MDTextFieldRect", module="kivymd.uix.textfield") 88 | r("MDToolbar", module="kivymd.uix.toolbar") 89 | r("MDBottomAppBar", module="kivymd.uix.toolbar") 90 | r("MDDropDownItem", module="kivymd.uix.dropdownitem") 91 | r("MDCircularLayout", module="kivymd.uix.circularlayout") 92 | -------------------------------------------------------------------------------- /kivymd/font_definitions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Themes/Font Definitions 3 | ======================= 4 | 5 | .. seealso:: 6 | 7 | `Material Design spec, The type system `_ 8 | """ 9 | 10 | from kivy.core.text import LabelBase 11 | 12 | from kivymd import fonts_path 13 | 14 | fonts = [ 15 | { 16 | "name": "Roboto", 17 | "fn_regular": fonts_path + "Roboto-Regular.ttf", 18 | "fn_bold": fonts_path + "Roboto-Bold.ttf", 19 | "fn_italic": fonts_path + "Roboto-Italic.ttf", 20 | "fn_bolditalic": fonts_path + "Roboto-BoldItalic.ttf", 21 | }, 22 | { 23 | "name": "RobotoThin", 24 | "fn_regular": fonts_path + "Roboto-Thin.ttf", 25 | "fn_italic": fonts_path + "Roboto-ThinItalic.ttf", 26 | }, 27 | { 28 | "name": "RobotoLight", 29 | "fn_regular": fonts_path + "Roboto-Light.ttf", 30 | "fn_italic": fonts_path + "Roboto-LightItalic.ttf", 31 | }, 32 | { 33 | "name": "RobotoMedium", 34 | "fn_regular": fonts_path + "Roboto-Medium.ttf", 35 | "fn_italic": fonts_path + "Roboto-MediumItalic.ttf", 36 | }, 37 | { 38 | "name": "RobotoBlack", 39 | "fn_regular": fonts_path + "Roboto-Black.ttf", 40 | "fn_italic": fonts_path + "Roboto-BlackItalic.ttf", 41 | }, 42 | { 43 | "name": "Marvel-Bold", 44 | "fn_regular": fonts_path + "Marvel-Bold.ttf", 45 | "fn_italic": fonts_path + "Marvel-Bold.ttf", 46 | }, 47 | { 48 | "name": "Poppins", 49 | "fn_regular": fonts_path + "Poppins-Regular.ttf", 50 | "fn_bold": fonts_path + "Poppins-Bold.ttf", 51 | }, 52 | { 53 | "name": "BigCircleFont", 54 | "fn_regular": fonts_path + "DejavuSans.ttf", 55 | }, 56 | { 57 | "name": "Icons", 58 | "fn_regular": fonts_path + "materialdesignicons-webfont.ttf", 59 | }, 60 | ] 61 | 62 | for font in fonts: 63 | LabelBase.register(**font) 64 | 65 | theme_font_styles = [ 66 | "H1", 67 | "H2", 68 | "H3", 69 | "H4", 70 | "H5", 71 | "H6", 72 | "Subtitle1", 73 | "Subtitle2", 74 | "Body1", 75 | "Body2", 76 | "Button", 77 | "Caption", 78 | "Overline", 79 | "Icon", 80 | ] 81 | """ 82 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles-2.png 83 | """ 84 | 85 | -------------------------------------------------------------------------------- /kivymd/fonts/DejavuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/DejavuSans.ttf -------------------------------------------------------------------------------- /kivymd/fonts/DejavuSansBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/DejavuSansBold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Marvel-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Marvel-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /kivymd/images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/folder.png -------------------------------------------------------------------------------- /kivymd/images/kivy-logo-white-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/kivy-logo-white-512.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/kivymd_512.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/kivymd_alpha.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/kivymd_logo.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/quad_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/quad_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/quad_shadow-2.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/rec_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/rec_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"rec_shadow-1.png": {"20": [2, 266, 256, 128], "21": [260, 266, 256, 128], "22": [518, 266, 256, 128], "23": [776, 266, 256, 128], "3": [260, 136, 256, 128], "2": [2, 136, 256, 128], "5": [776, 136, 256, 128], "4": [518, 136, 256, 128], "7": [260, 6, 256, 128], "6": [2, 6, 256, 128], "9": [776, 6, 256, 128], "8": [518, 6, 256, 128]}, "rec_shadow-0.png": {"11": [518, 266, 256, 128], "10": [260, 266, 256, 128], "13": [2, 136, 256, 128], "12": [776, 266, 256, 128], "15": [518, 136, 256, 128], "14": [260, 136, 256, 128], "17": [2, 6, 256, 128], "16": [776, 136, 256, 128], "19": [518, 6, 256, 128], "18": [260, 6, 256, 128], "1": [776, 6, 256, 128], "0": [2, 266, 256, 128]}} -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/rec_st_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/rec_st_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/rec_st_shadow-2.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}} -------------------------------------------------------------------------------- /kivymd/images/round_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/round_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/round_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/round_shadow-2.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} -------------------------------------------------------------------------------- /kivymd/images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/images/transparent.png -------------------------------------------------------------------------------- /kivymd/material_resources.py: -------------------------------------------------------------------------------- 1 | """ 2 | Material Resources 3 | ================== 4 | """ 5 | 6 | import os 7 | 8 | from kivy.core.window import Window 9 | from kivy.metrics import dp 10 | from kivy.utils import platform 11 | 12 | if "KIVY_DOC_INCLUDE" in os.environ: 13 | dp = lambda x: x # NOQA: F811 14 | 15 | # Feel free to override this const if you're designing for a device such as 16 | # a GNU/Linux tablet. 17 | DEVICE_IOS = platform == "ios" or platform == "macosx" 18 | if platform != "android" and platform != "ios": 19 | DEVICE_TYPE = "desktop" 20 | elif Window.width >= dp(600) and Window.height >= dp(600): 21 | DEVICE_TYPE = "tablet" 22 | else: 23 | DEVICE_TYPE = "mobile" 24 | 25 | if DEVICE_TYPE == "mobile": 26 | MAX_NAV_DRAWER_WIDTH = dp(300) 27 | HORIZ_MARGINS = dp(16) 28 | STANDARD_INCREMENT = dp(56) 29 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 30 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8) 31 | else: 32 | MAX_NAV_DRAWER_WIDTH = dp(400) 33 | HORIZ_MARGINS = dp(24) 34 | STANDARD_INCREMENT = dp(64) 35 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 36 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT 37 | 38 | TOUCH_TARGET_HEIGHT = dp(48) 39 | -------------------------------------------------------------------------------- /kivymd/stiffscroll/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 LogicalDash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /kivymd/stiffscroll/README.md: -------------------------------------------------------------------------------- 1 | stiffscroll 2 | =========== 3 | 4 | A ScrollEffect for use with a Kivy ScrollView. It makes scrolling more 5 | laborious as you reach the edge of the scrollable area. 6 | 7 | A ScrollView constructed with StiffScrollEffect, 8 | eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to 9 | scroll as you get nearer to its edges. You can scroll all the way to 10 | the edge if you want to, but it will take more finger-movement than 11 | usual. 12 | 13 | Unlike DampedScrollEffect, it is impossible to overscroll with 14 | StiffScrollEffect. That means you cannot push the contents of the 15 | ScrollView far enough to see what's beneath them. This is appropriate 16 | if the ScrollView contains, eg., a background image, like a desktop 17 | wallpaper. Overscrolling may give the impression that there is some 18 | reason to overscroll, even if just to take a peek beneath, and that 19 | impression may be misleading. 20 | 21 | StiffScrollEffect was written by Zachary Spector. His other stuff is at: 22 | https://github.com/LogicalDash/ 23 | He can be reached, and possibly hired, at: 24 | zacharyspector@gmail.com -------------------------------------------------------------------------------- /kivymd/tests/pyinstaller/test_pyinstaller_packaging.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyInstaller freezing test 3 | ========================= 4 | 5 | PyInstaller must package KivyMD apps correctly. 6 | """ 7 | 8 | import subprocess 9 | 10 | from PyInstaller import __main__ as pyi_main 11 | 12 | 13 | def test_datas(tmp_path): 14 | """Test fonts and images.""" 15 | app_name = "userapp" 16 | workpath = tmp_path / "build" 17 | distpath = tmp_path / "dist" 18 | app = tmp_path / (app_name + ".py") 19 | app.write_text( 20 | """ 21 | import os 22 | import kivymd 23 | from kivy.core.text import LabelBase 24 | 25 | fonts = os.listdir(kivymd.fonts_path) 26 | print(fonts) 27 | assert "Roboto-Regular.ttf" in fonts 28 | assert "materialdesignicons-webfont.ttf" in fonts 29 | print(LabelBase._fonts.keys()) 30 | assert "Roboto" in LabelBase._fonts.keys() # NOQA 31 | assert "Icons" in LabelBase._fonts.keys() # NOQA 32 | 33 | images = os.listdir(kivymd.images_path) 34 | print(images) 35 | assert "folder.png" in images 36 | assert "rec_shadow.atlas" in images 37 | """ 38 | ) 39 | pyi_main.run( 40 | [ 41 | "--workpath", 42 | str(workpath), 43 | "--distpath", 44 | str(distpath), 45 | "--specpath", 46 | str(tmp_path), 47 | str(app), 48 | ] 49 | ) 50 | subprocess.run([str(distpath / app_name / app_name)], check=True) 51 | 52 | 53 | def test_widgets(tmp_path): 54 | """Test that all widgets are accesible.""" 55 | app_name = "userapp" 56 | workpath = tmp_path / "build" 57 | distpath = tmp_path / "dist" 58 | app = tmp_path / (app_name + ".py") 59 | app.write_text( 60 | """ 61 | import os 62 | import kivymd # NOQA 63 | __import__("kivymd.uix.label") 64 | __import__("kivymd.uix.button") 65 | __import__("kivymd.uix.list") 66 | __import__("kivymd.uix.navigationdrawer") 67 | 68 | print(os.listdir(os.path.dirname(kivymd.uix.__path__[0]))) 69 | """ 70 | ) 71 | pyi_main.run( 72 | [ 73 | "--workpath", 74 | str(workpath), 75 | "--distpath", 76 | str(distpath), 77 | "--specpath", 78 | str(tmp_path), 79 | str(app), 80 | ] 81 | ) 82 | subprocess.run([str(distpath / app_name / app_name)], check=True) 83 | -------------------------------------------------------------------------------- /kivymd/tests/test_app.py: -------------------------------------------------------------------------------- 1 | from kivy import lang 2 | from kivy.clock import Clock 3 | from kivy.tests.common import GraphicUnitTest 4 | 5 | from kivymd.app import MDApp 6 | from kivymd.theming import ThemeManager 7 | 8 | 9 | class AppTest(GraphicUnitTest): 10 | def test_start_raw_app(self): 11 | lang._delayed_start = None 12 | a = MDApp() 13 | Clock.schedule_once(a.stop, 0.1) 14 | a.run() 15 | 16 | def test_theme_manager_existance(self): 17 | lang._delayed_start = None 18 | a = MDApp() 19 | Clock.schedule_once(a.stop, 0.1) 20 | a.run() 21 | assert isinstance(a.theme_cls, ThemeManager) 22 | -------------------------------------------------------------------------------- /kivymd/tests/test_font_definitions.py: -------------------------------------------------------------------------------- 1 | def test_fonts_registration(): 2 | # This should register fonts: 3 | from kivy.core.text import LabelBase 4 | 5 | import kivymd # NOQA 6 | 7 | fonts = [ 8 | "Roboto", 9 | "RobotoThin", 10 | "RobotoLight", 11 | "RobotoMedium", 12 | "RobotoBlack", 13 | "Icons", 14 | ] 15 | for font in fonts: 16 | assert font in LabelBase._fonts.keys() 17 | -------------------------------------------------------------------------------- /kivymd/tests/test_icon_definitions.py: -------------------------------------------------------------------------------- 1 | def test_icons_have_size(): 2 | from kivy.core.text import Label 3 | 4 | from kivymd.icon_definitions import md_icons 5 | 6 | lbl = Label(font_name="Icons") 7 | for icon_name, icon_value in md_icons.items(): 8 | assert len(icon_value) == 1 9 | lbl.refresh() 10 | assert lbl.get_extents(icon_value) is not None 11 | -------------------------------------------------------------------------------- /kivymd/theming_dynamic_text.py: -------------------------------------------------------------------------------- 1 | """ 2 | Theming Dynamic Text 3 | ==================== 4 | 5 | Two implementations. The first is based on color brightness obtained from- 6 | https://www.w3.org/TR/AERT#color-contrast 7 | The second is based on relative luminance calculation for sRGB obtained from- 8 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef 9 | and contrast ratio calculation obtained from- 10 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef 11 | 12 | Preliminary testing suggests color brightness more closely matches the 13 | `Material Design spec` suggested text colors, but the alternative implementation 14 | is both newer and the current 'correct' recommendation, so is included here 15 | as an option. 16 | """ 17 | 18 | 19 | def _color_brightness(color): 20 | # Implementation of color brightness method 21 | brightness = color[0] * 299 + color[1] * 587 + color[2] * 114 22 | brightness = brightness 23 | return brightness 24 | 25 | 26 | def _black_or_white_by_color_brightness(color): 27 | if _color_brightness(color) >= 500: 28 | return "black" 29 | else: 30 | return "white" 31 | 32 | 33 | def _normalized_channel(color): 34 | # Implementation of contrast ratio and relative luminance method 35 | if color <= 0.03928: 36 | return color / 12.92 37 | else: 38 | return ((color + 0.055) / 1.055) ** 2.4 39 | 40 | 41 | def _luminance(color): 42 | rg = _normalized_channel(color[0]) 43 | gg = _normalized_channel(color[1]) 44 | bg = _normalized_channel(color[2]) 45 | return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg 46 | 47 | 48 | def _black_or_white_by_contrast_ratio(color): 49 | l_color = _luminance(color) 50 | l_black = 0.0 51 | l_white = 1.0 52 | b_contrast = (l_color + 0.05) / (l_black + 0.05) 53 | w_contrast = (l_white + 0.05) / (l_color + 0.05) 54 | return "white" if w_contrast >= b_contrast else "black" 55 | 56 | 57 | def get_contrast_text_color(color, use_color_brightness=True): 58 | if use_color_brightness: 59 | contrast_color = _black_or_white_by_color_brightness(color) 60 | else: 61 | contrast_color = _black_or_white_by_contrast_ratio(color) 62 | if contrast_color == "white": 63 | return 1, 1, 1, 1 64 | else: 65 | return 0, 0, 0, 1 66 | 67 | 68 | if __name__ == "__main__": 69 | from kivy.utils import get_color_from_hex 70 | 71 | from kivymd.color_definitions import colors, text_colors 72 | 73 | for c in colors.items(): 74 | if c[0] in ["Light", "Dark"]: 75 | continue 76 | color = c[0] 77 | print(f"For the {color} color palette:") 78 | for name, hex_color in c[1].items(): 79 | if hex_color: 80 | col = get_color_from_hex(hex_color) 81 | col_bri = get_contrast_text_color(col) 82 | con_rat = get_contrast_text_color( 83 | col, use_color_brightness=False 84 | ) 85 | text_color = text_colors[c[0]][name] 86 | print( 87 | f" The {name} hue gives {col_bri} using color " 88 | f"brightness, {con_rat} using contrast ratio, and " 89 | f"{text_color} from the MD spec" 90 | ) 91 | -------------------------------------------------------------------------------- /kivymd/toast/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Brian - androidtoast library 4 | Copyright (c) 2019 Ivanov Yuri - kivytoast library 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /kivymd/toast/README.md: -------------------------------------------------------------------------------- 1 | KivyToast 2 | ======== 3 | 4 | A package for working with messages like Toast on Android. It is intended for use in applications written using the Kivy framework. 5 | 6 | This package is an improved version of the package https://github.com/knappador/kivy-toaster in which human toasts are written, written on Kivy. 7 | 8 | 9 | 10 | The package modules are written using the framework for cross-platform development of . 11 | Information about the framework is available at http://kivy.org. 12 | 13 | An example of usage (note that with this import the native implementation of toasts will be used for the Android platform and implementation on Kivy for others: 14 | 15 | ```python 16 | from toast import toast 17 | 18 | ... 19 | 20 | # And then in the code, toasts are available 21 | # by calling the toast function: 22 | toast ('Your message') 23 | ``` 24 | 25 | To force the Kivy implementation on the Android platform, use the import of the form: 26 | 27 | ```python 28 | from toast.kivytoast import toast 29 | ``` 30 | 31 | PROGRAMMING LANGUAGE 32 | -------------------- 33 | Python 2.7 + 34 | 35 | DEPENDENCE 36 | ---------- 37 | The [Kivy] framework (http://kivy.org/docs/installation/installation.html) 38 | 39 | LICENSE 40 | ------- 41 | MIT -------------------------------------------------------------------------------- /kivymd/toast/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ("toast",) 2 | 3 | from kivy.utils import platform 4 | 5 | if platform == "android": 6 | try: 7 | from .androidtoast import toast 8 | except BaseException: 9 | from .kivytoast import toast 10 | else: 11 | from .kivytoast import toast 12 | -------------------------------------------------------------------------------- /kivymd/toast/androidtoast/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Toast for Android device 3 | ======================== 4 | 5 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toast.png 6 | :align: center 7 | 8 | """ 9 | 10 | __all__ = ("toast",) 11 | 12 | from .androidtoast import toast 13 | -------------------------------------------------------------------------------- /kivymd/toast/androidtoast/androidtoast.py: -------------------------------------------------------------------------------- 1 | """ 2 | AndroidToast 3 | ============ 4 | 5 | .. rubric:: Native implementation of toast for Android devices. 6 | 7 | .. code-block:: python 8 | 9 | # Will be automatically used native implementation of the toast 10 | # if your application is running on an Android device. 11 | # Otherwise, will be used toast implementation 12 | # from the kivymd/toast/kivytoast package. 13 | 14 | from kivy.lang import Builder 15 | from kivy.uix.screenmanager import ScreenManager 16 | 17 | from kivymd.toast import toast 18 | from kivymd.app import MDApp 19 | 20 | KV = ''' 21 | MDScreen: 22 | 23 | MDFlatButton: 24 | text: "My Toast" 25 | pos_hint:{"center_x": .5, "center_y": .5} 26 | on_press: app.show_toast() 27 | ''' 28 | 29 | 30 | class Test(MDApp): 31 | def build(self): 32 | return Builder.load_string(KV) 33 | 34 | def show_toast(self): 35 | toast("Hello World", True, 80, 200, 0) 36 | 37 | 38 | Test().run() 39 | """ 40 | __all__ = ("toast",) 41 | 42 | from android.runnable import run_on_ui_thread 43 | from jnius import autoclass 44 | 45 | activity = autoclass("org.kivy.android.PythonActivity").mActivity 46 | Toast = autoclass("android.widget.Toast") 47 | String = autoclass("java.lang.String") 48 | 49 | 50 | @run_on_ui_thread 51 | def toast(text, length_long=False, gravity=80, y=150, x=0): 52 | """ 53 | Displays a toast. 54 | 55 | :param length_long: the amount of time (in seconds) that the toast is 56 | visible on the screen. 57 | :param text: text to be displayed in the toast; 58 | :param gravity: refers to the toast position, if it is 80the toast will 59 | be shown below, if it is 40 the toast will be displayed above; 60 | :param y: refers to the vertical position of the toast; 61 | :param x: refers to the horizontal position of the toast; 62 | 63 | Important: if only the text value is specified and the value of 64 | the `gravity`, `y`, `x` parameters is not specified, their values ​​will 65 | be 0 which means that the toast will be shown in the center. 66 | """ 67 | 68 | duration = Toast.LENGTH_LONG if length_long else Toast.LENGTH_SHORT 69 | t = Toast.makeText(activity, String(text), duration) 70 | t.setGravity(gravity, x, y) 71 | t.show() 72 | -------------------------------------------------------------------------------- /kivymd/toast/kivytoast/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ("toast",) 2 | 3 | from .kivytoast import toast 4 | -------------------------------------------------------------------------------- /kivymd/toast/kivytoast/kivytoast.py: -------------------------------------------------------------------------------- 1 | """ 2 | KivyToast 3 | ========= 4 | 5 | .. rubric:: Implementation of toasts for desktop. 6 | 7 | .. code-block:: python 8 | 9 | from kivy.lang import Builder 10 | 11 | from kivymd.app import MDApp 12 | from kivymd.toast import toast 13 | 14 | KV = ''' 15 | MDScreen: 16 | 17 | MDToolbar: 18 | title: 'Test Toast' 19 | pos_hint: {'top': 1} 20 | left_action_items: [['menu', lambda x: x]] 21 | 22 | MDRaisedButton: 23 | text: 'TEST KIVY TOAST' 24 | pos_hint: {'center_x': .5, 'center_y': .5} 25 | on_release: app.show_toast() 26 | ''' 27 | 28 | 29 | class Test(MDApp): 30 | def show_toast(self): 31 | '''Displays a toast on the screen.''' 32 | 33 | toast('Test Kivy Toast') 34 | 35 | def build(self): 36 | return Builder.load_string(KV) 37 | 38 | Test().run() 39 | """ 40 | 41 | from kivy.animation import Animation 42 | from kivy.clock import Clock 43 | from kivy.core.window import Window 44 | from kivy.lang import Builder 45 | from kivy.metrics import dp 46 | from kivy.properties import ListProperty, NumericProperty 47 | from kivy.uix.label import Label 48 | 49 | from kivymd.uix.dialog import BaseDialog 50 | 51 | Builder.load_string( 52 | """ 53 | : 54 | size_hint: (None, None) 55 | pos_hint: {"center_x": 0.5, "center_y": 0.1} 56 | opacity: 0 57 | auto_dismiss: True 58 | overlay_color: [0, 0, 0, 0] 59 | canvas: 60 | Color: 61 | rgba: root._md_bg_color 62 | RoundedRectangle: 63 | pos: self.pos 64 | size: self.size 65 | radius: root.radius 66 | """ 67 | ) 68 | 69 | 70 | class Toast(BaseDialog): 71 | duration = NumericProperty(2.5) 72 | """ 73 | The amount of time (in seconds) that the toast is visible on the screen. 74 | 75 | :attr:`duration` is an :class:`~kivy.properties.NumericProperty` 76 | and defaults to `2.5`. 77 | """ 78 | 79 | _md_bg_color = ListProperty() 80 | 81 | def __init__(self, **kwargs): 82 | super().__init__(**kwargs) 83 | self.label_toast = Label(size_hint=(None, None), opacity=0) 84 | self.label_toast.bind(texture_size=self.label_check_texture_size) 85 | self.add_widget(self.label_toast) 86 | 87 | def label_check_texture_size(self, instance, texture_size): 88 | texture_width, texture_height = texture_size 89 | if texture_width > Window.width: 90 | instance.text_size = (Window.width - dp(10), None) 91 | instance.texture_update() 92 | texture_width, texture_height = instance.texture_size 93 | self.size = (texture_width + 25, texture_height + 25) 94 | 95 | def toast(self, text_toast): 96 | self.label_toast.text = text_toast 97 | self.open() 98 | 99 | def on_open(self): 100 | self.fade_in() 101 | Clock.schedule_once(self.fade_out, self.duration) 102 | 103 | def fade_in(self): 104 | anim = Animation(opacity=1, duration=0.4) 105 | anim.start(self.label_toast) 106 | anim.start(self) 107 | 108 | def fade_out(self, *args): 109 | anim = Animation(opacity=0, duration=0.4) 110 | anim.bind(on_complete=lambda *x: self.dismiss()) 111 | anim.start(self.label_toast) 112 | anim.start(self) 113 | 114 | def on_touch_down(self, touch): 115 | if not self.collide_point(*touch.pos): 116 | if self.auto_dismiss: 117 | self.fade_out() 118 | return False 119 | super().on_touch_down(touch) 120 | return True 121 | 122 | 123 | def toast(text="", background=[0.2, 0.2, 0.2, 1], duration=2.5): 124 | """Displays a toast. 125 | 126 | :attr duration: the amount of time (in seconds) that the toast is visible on the screen 127 | :type duration: float 128 | 129 | :attr background: color ``rgba`` in Kivy format 130 | :type background: list 131 | """ 132 | 133 | Toast(duration=duration, _md_bg_color=background).toast(text) 134 | -------------------------------------------------------------------------------- /kivymd/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/tools/__init__.py -------------------------------------------------------------------------------- /kivymd/tools/packaging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/tools/packaging/__init__.py -------------------------------------------------------------------------------- /kivymd/tools/packaging/pyinstaller/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyInstaller hooks 3 | ================= 4 | 5 | Add ``hookspath=[kivymd.hooks_path]`` to your .spec file. 6 | 7 | Example of .spec file 8 | ===================== 9 | 10 | .. code-block:: python 11 | 12 | # -*- mode: python ; coding: utf-8 -*- 13 | 14 | import sys 15 | import os 16 | 17 | from kivy_deps import sdl2, glew 18 | 19 | from kivymd import hooks_path as kivymd_hooks_path 20 | 21 | path = os.path.abspath(".") 22 | 23 | a = Analysis( 24 | ["main.py"], 25 | pathex=[path], 26 | hookspath=[kivymd_hooks_path], 27 | win_no_prefer_redirects=False, 28 | win_private_assemblies=False, 29 | cipher=None, 30 | noarchive=False, 31 | ) 32 | pyz = PYZ(a.pure, a.zipped_data, cipher=None) 33 | 34 | exe = EXE( 35 | pyz, 36 | a.scripts, 37 | a.binaries, 38 | a.zipfiles, 39 | a.datas, 40 | *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], 41 | debug=False, 42 | strip=False, 43 | upx=True, 44 | name="app_name", 45 | console=True, 46 | ) 47 | """ 48 | 49 | __all__ = ("hooks_path", "get_hook_dirs", "get_pyinstaller_tests") 50 | 51 | import os 52 | from pathlib import Path 53 | 54 | import kivymd 55 | 56 | hooks_path = str(Path(__file__).absolute().parent) 57 | """Path to hook directory to use with PyInstaller. 58 | See :mod:`kivymd.tools.packaging.pyinstaller` for more information.""" 59 | 60 | 61 | def get_hook_dirs(): 62 | return [hooks_path] 63 | 64 | 65 | def get_pyinstaller_tests(): 66 | return [os.path.join(kivymd.path, "tests", "pyinstaller")] 67 | 68 | 69 | if __name__ == "__main__": 70 | print(hooks_path) 71 | print(get_hook_dirs()) 72 | print(get_pyinstaller_tests()) 73 | -------------------------------------------------------------------------------- /kivymd/tools/packaging/pyinstaller/hook-kivymd.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyInstaller hook for KivyMD 3 | =========================== 4 | 5 | Adds fonts and images to package. 6 | 7 | All modules from uix directory are added by Kivy hook. 8 | """ 9 | 10 | from pathlib import Path 11 | 12 | import kivymd 13 | 14 | datas = [ 15 | ( 16 | kivymd.fonts_path, 17 | str(Path("kivymd").joinpath(Path(kivymd.fonts_path).name)), 18 | ), 19 | ( 20 | kivymd.images_path, 21 | str(Path("kivymd").joinpath(Path(kivymd.images_path).name)), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /kivymd/tools/release/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/tools/release/__init__.py -------------------------------------------------------------------------------- /kivymd/tools/release/argument_parser.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Artem Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | import argparse 7 | import sys 8 | 9 | 10 | class ArgumentParserWithHelp(argparse.ArgumentParser): 11 | def parse_args(self, args=None, namespace=None): 12 | # Add help when no arguments specified 13 | if not args and not len(sys.argv) > 1: 14 | self.print_help() 15 | self.exit(1) 16 | return super().parse_args(args, namespace) 17 | 18 | def error(self, message): 19 | # Add full help on error 20 | self.print_help() 21 | self.exit(2, f"\nError: {message}\n") 22 | 23 | def format_help(self): 24 | # Add subparsers usage and help to full help text 25 | formatter = self._get_formatter() 26 | 27 | # Get subparsers 28 | subparsers_actions = [ 29 | action 30 | for action in self._actions 31 | if isinstance(action, argparse._SubParsersAction) 32 | ] 33 | 34 | # Description 35 | formatter.add_text(self.description) 36 | 37 | # Usage 38 | formatter.add_usage( 39 | self.usage, 40 | self._actions, 41 | self._mutually_exclusive_groups, 42 | prefix="Usage:\n", 43 | ) 44 | 45 | # Subparsers usage 46 | for subparsers_action in subparsers_actions: 47 | for choice, subparser in subparsers_action.choices.items(): 48 | formatter.add_usage( 49 | subparser.usage, 50 | subparser._actions, 51 | subparser._mutually_exclusive_groups, 52 | prefix="", 53 | ) 54 | 55 | # Positionals, optionals and user-defined groups 56 | for action_group in self._action_groups: 57 | if not any( 58 | [ 59 | action in subparsers_actions 60 | for action in action_group._group_actions 61 | ] 62 | ): 63 | formatter.start_section(action_group.title) 64 | formatter.add_text(action_group.description) 65 | formatter.add_arguments(action_group._group_actions) 66 | formatter.end_section() 67 | else: 68 | # Process subparsers differently 69 | # Just show list of choices 70 | formatter.start_section(action_group.title) 71 | # formatter.add_text(action_group.description) 72 | for action in action_group._group_actions: 73 | for choice in action.choices: 74 | formatter.add_text(choice) 75 | formatter.end_section() 76 | 77 | # Subparsers help 78 | for subparsers_action in subparsers_actions: 79 | for choice, subparser in subparsers_action.choices.items(): 80 | formatter.start_section(choice) 81 | for action_group in subparser._action_groups: 82 | formatter.start_section(action_group.title) 83 | formatter.add_text(action_group.description) 84 | formatter.add_arguments(action_group._group_actions) 85 | formatter.end_section() 86 | formatter.end_section() 87 | 88 | # Epilog 89 | formatter.add_text(self.epilog) 90 | 91 | # Determine help from format above 92 | return formatter.format_help() 93 | -------------------------------------------------------------------------------- /kivymd/tools/release/git_commands.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Artem Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | import subprocess 7 | 8 | 9 | def command(cmd: list, capture_output: bool = False) -> str: 10 | """Run system command.""" 11 | print(f"Command: {subprocess.list2cmdline(cmd)}") 12 | if capture_output: 13 | out = subprocess.check_output(cmd) 14 | out = out.decode("utf-8") 15 | print(out.strip()) 16 | return out 17 | else: 18 | subprocess.check_call(cmd) 19 | return "" 20 | 21 | 22 | def get_previous_version() -> str: 23 | """Returns latest tag in git.""" 24 | command(["git", "checkout", "master"]) 25 | old_version = command( 26 | ["git", "describe", "--abbrev=0", "--tags"], capture_output=True 27 | ) 28 | old_version = old_version[:-1] # Remove \n 29 | return old_version 30 | 31 | 32 | def git_clean(ask: bool = True): 33 | """Clean git repository from untracked and changed files.""" 34 | # Check what files will be removed 35 | files_to_clean = command( 36 | ["git", "clean", "-dx", "--force", "--dry-run"], capture_output=True 37 | ).strip() 38 | # Ask before removing 39 | if ask and files_to_clean: 40 | while True: 41 | ans = input("Do you want to remove these files? (yes/no)").lower() 42 | if ans == "y" or ans == "yes": 43 | break 44 | elif ans == "n" or ans == "no": 45 | print("git clean is required. Exit") 46 | exit(0) 47 | 48 | # Remove all untracked files 49 | command(["git", "clean", "-dx", "--force"]) 50 | command(["git", "reset", "--hard"]) 51 | 52 | 53 | def git_commit(message: str, allow_error: bool = False, add_files: list = None): 54 | """Make commit.""" 55 | add_files = add_files if add_files else ["-A"] 56 | command(["git", "add", *add_files]) 57 | try: 58 | command(["git", "commit", "--all", "-m", message]) 59 | except subprocess.CalledProcessError as e: 60 | if not allow_error: 61 | raise e 62 | 63 | 64 | def git_tag(name: str): 65 | """Create tag.""" 66 | command(["git", "tag", name]) 67 | 68 | 69 | def git_push(branches_to_push: list, ask: bool = True, push: bool = False): 70 | """Push all changes.""" 71 | if ask: 72 | push = input("Do you want to push changes? (y)") in ( 73 | "", 74 | "y", 75 | "yes", 76 | ) 77 | 78 | cmd = ["git", "push", "--tags", "origin", "master", *branches_to_push] 79 | if push: 80 | command(cmd) 81 | else: 82 | print( 83 | f"Changes are not pushed. Command for manual pushing: {subprocess.list2cmdline(cmd)}" 84 | ) 85 | 86 | 87 | if __name__ == "__main__": 88 | git_clean(ask=True) 89 | -------------------------------------------------------------------------------- /kivymd/tools/release/update_icons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Artem Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | """ 7 | Tool for updating Iconic font 8 | ============================= 9 | 10 | Downloads archive from https://github.com/Templarian/MaterialDesign-Webfont and 11 | updates font file with icon_definitions. 12 | """ 13 | 14 | import json 15 | import os 16 | import re 17 | import shutil 18 | import sys 19 | import zipfile 20 | 21 | import requests 22 | 23 | from kivymd.tools.release.git_commands import git_commit 24 | 25 | # Paths to files in kivymd repository 26 | kivymd_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 27 | font_path = os.path.join( 28 | kivymd_path, "fonts", "materialdesignicons-webfont.ttf" 29 | ) 30 | icon_definitions_path = os.path.join(kivymd_path, "icon_definitions.py") 31 | 32 | font_version = "master" 33 | # URL to download new archive (set None if already downloaded) 34 | url = ( 35 | f"https://github.com/Templarian/MaterialDesign-Webfont" 36 | f"/archive/{font_version}.zip" 37 | ) 38 | # url = None 39 | 40 | # Paths to files in loaded archive 41 | temp_path = os.path.join(os.path.dirname(__file__), "temp") 42 | temp_repo_path = os.path.join( 43 | temp_path, f"MaterialDesign-Webfont-{font_version}" 44 | ) 45 | temp_font_path = os.path.join( 46 | temp_repo_path, "fonts", "materialdesignicons-webfont.ttf" 47 | ) 48 | temp_preview_path = os.path.join(temp_repo_path, "preview.html") 49 | 50 | # Regex 51 | re_icons_json = re.compile(r"(?<=var icons = )[\S ]+(?=;)") 52 | re_additional_icons = re.compile(r"(?<=icons\.push\()[\S ]+(?=\);)") 53 | re_version = re.compile(r"(?<=)[\d.]+(?=)") 54 | re_quote_keys = re.compile(r"([{\s,])(\w+)(:)") 55 | re_icon_definitions = re.compile(r"md_icons = {\n([ ]{4}[\s\S]*,\n)*}") 56 | re_version_in_file = re.compile(r"(?<=LAST UPDATED: Version )[\d.]+(?=\n)") 57 | 58 | 59 | def download_file(url, path): 60 | response = requests.get(url, stream=True) 61 | if response.status_code != 200: 62 | return False 63 | with open(path, "wb") as f: 64 | shutil.copyfileobj(response.raw, f) 65 | return True 66 | 67 | 68 | def unzip_archive(archive_path, dir_path): 69 | with zipfile.ZipFile(archive_path, "r") as zip_ref: 70 | zip_ref.extractall(dir_path) 71 | 72 | 73 | def get_icons_list(): 74 | # There is js array with icons in file preview.html 75 | with open(temp_preview_path, "r") as f: 76 | preview_file = f.read() 77 | # Find version 78 | version = re_version.findall(preview_file)[0] 79 | # Load icons 80 | jsons_icons = re_icons_json.findall(preview_file)[0] 81 | json_icons = re_quote_keys.sub(r'\1"\2"\3', jsons_icons) 82 | icons = json.loads(json_icons) 83 | # Find additional icons (like a blank icon) 84 | # jsons_additional_icons = re_additional_icons.findall(preview_file) 85 | # for j in jsons_additional_icons: 86 | # json_additional_icons = re_quote_keys.sub(r'\1"\2"\3', j) 87 | # icons.append(json.loads(json_additional_icons)) 88 | return icons, version 89 | 90 | 91 | def make_icon_definitions(icons): 92 | # Make python dict ("name": hex) 93 | icon_definitions = "md_icons = {\n" 94 | for i in icons: 95 | icon_definitions += " " * 4 96 | if len(i["hex"]) != 4: 97 | # Some icons has 5-digit unicode 98 | i["hex"] = "0" * (8 - len(i["hex"])) + i["hex"] 99 | icon_definitions += f'"{i["name"]}": "\\U{i["hex"].upper()}",\n' 100 | else: 101 | icon_definitions += f'"{i["name"]}": "\\u{i["hex"].upper()}",\n' 102 | icon_definitions += " " * 4 + '"blank": " ",\n' # Add blank icon (space) 103 | icon_definitions += "}" 104 | return icon_definitions 105 | 106 | 107 | def export_icon_definitions(icon_definitions, version): 108 | with open(icon_definitions_path, "r") as f: 109 | icon_definitions_file = f.read() 110 | # Change md_icons list 111 | new_icon_definitions = re_icon_definitions.sub( 112 | icon_definitions.replace("\\", "\\\\"), icon_definitions_file, 1 113 | ) 114 | # Change version 115 | new_icon_definitions = re_version_in_file.sub( 116 | version, new_icon_definitions, 1 117 | ) 118 | with open(icon_definitions_path, "w") as f: 119 | f.write(new_icon_definitions) 120 | 121 | 122 | def update_icons(make_commit: bool = False): 123 | if url is not None: 124 | print(f"Downloading Material Design Icons from {url}") 125 | if download_file(url, "iconic-font.zip"): 126 | print("Archive downloaded") 127 | else: 128 | print("Error: Could not download archive", file=sys.stderr) 129 | else: 130 | print("URL is None. Do not download archive") 131 | if os.path.exists("iconic-font.zip"): 132 | unzip_archive("iconic-font.zip", temp_path) 133 | print("Unzip successful") 134 | os.remove("iconic-font.zip") 135 | if os.path.exists(temp_repo_path): 136 | shutil.copy2(temp_font_path, font_path) 137 | print("Font copied") 138 | icons, version = get_icons_list() 139 | print(f"Version {version}. {len(icons)} icons loaded") 140 | icon_definitions = make_icon_definitions(icons) 141 | export_icon_definitions(icon_definitions, version) 142 | print("File icon_definitions.py updated") 143 | shutil.rmtree(temp_path, ignore_errors=True) 144 | 145 | if make_commit: 146 | git_commit( 147 | f"Update Iconic font (v{version})", 148 | allow_error=True, 149 | add_files=[ 150 | "kivymd/icon_definitions.py", 151 | "kivymd/fonts/materialdesignicons-webfont.ttf", 152 | ], 153 | ) 154 | print("\nSuccessful. You can now push changes") 155 | else: 156 | print( 157 | f'\nSuccessful. Commit message: "Update Iconic font (v{version})"' 158 | ) 159 | else: 160 | print(f"Error: {temp_repo_path} not exists", file=sys.stderr) 161 | exit(1) 162 | 163 | 164 | def main(): 165 | make_commit = "--commit" in sys.argv 166 | if "--commit" in sys.argv: 167 | sys.argv.remove("--commit") 168 | update_icons(make_commit=make_commit) 169 | 170 | 171 | if __name__ == "__main__": 172 | main() 173 | -------------------------------------------------------------------------------- /kivymd/tools/update_icons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Artem S. Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | """ 7 | Tool for updating Iconic font 8 | ============================= 9 | 10 | Downloads archive from https://github.com/Templarian/MaterialDesign-Webfont and 11 | updates font file with icon_definitions. 12 | """ 13 | 14 | import os 15 | import requests 16 | import zipfile 17 | import shutil 18 | import re 19 | import json 20 | 21 | os.chdir(os.path.dirname(__file__)) 22 | # Paths to files in kivymd repository 23 | font_path = "../fonts/materialdesignicons-webfont.ttf" 24 | icon_definitions_path = "../icon_definitions.py" 25 | 26 | font_version = "master" 27 | # URL to download new archive (set None if already downloaded) 28 | url = ( 29 | f"https://github.com/Templarian/MaterialDesign-Webfont" 30 | f"/archive/{font_version}.zip" 31 | ) 32 | # url = None 33 | 34 | # Paths to files in loaded archive 35 | temp_path = os.path.abspath("temp") 36 | temp_repo_path = f"{temp_path}/MaterialDesign-Webfont-{font_version}" 37 | temp_font_path = f"{temp_repo_path}/fonts/materialdesignicons-webfont.ttf" 38 | temp_preview_path = f"{temp_repo_path}/preview.html" 39 | 40 | # Regex 41 | re_icons_json = re.compile(r"(?<=var icons = )[\S ]+(?=;)") 42 | re_additional_icons = re.compile(r"(?<=icons\.push\()[\S ]+(?=\);)") 43 | re_version = re.compile(r"(?<=)[\d.]+(?=)") 44 | re_quote_keys = re.compile(r"([{\s,])(\w+)(:)") 45 | re_icon_definitions = re.compile(r"md_icons = {\n([ ]{4}[\s\S]*,\n)*}") 46 | re_version_in_file = re.compile(r"(?<=LAST UPDATED: Version )[\d.]+(?=\n)") 47 | 48 | 49 | def download_file(url, path): 50 | response = requests.get(url, stream=True) 51 | if response.status_code != 200: 52 | return False 53 | with open(path, "wb") as f: 54 | shutil.copyfileobj(response.raw, f) 55 | return True 56 | 57 | 58 | def unzip_archive(archive_path, dir_path): 59 | with zipfile.ZipFile(archive_path, "r") as zip_ref: 60 | zip_ref.extractall(dir_path) 61 | 62 | 63 | def get_icons_list(): 64 | # There is js array with icons in file preview.html 65 | with open(temp_preview_path, "r") as f: 66 | preview_file = f.read() 67 | # Find version 68 | version = re_version.findall(preview_file)[0] 69 | # Load icons 70 | jsons_icons = re_icons_json.findall(preview_file)[0] 71 | json_icons = re_quote_keys.sub(r'\1"\2"\3', jsons_icons) 72 | icons = json.loads(json_icons) 73 | # Find additional icons (like a blank icon) 74 | # jsons_additional_icons = re_additional_icons.findall(preview_file) 75 | # for j in jsons_additional_icons: 76 | # json_additional_icons = re_quote_keys.sub(r'\1"\2"\3', j) 77 | # icons.append(json.loads(json_additional_icons)) 78 | return icons, version 79 | 80 | 81 | def make_icon_definitions(icons): 82 | # Make python dict ("name": hex) 83 | icon_definitions = "md_icons = {\n" 84 | for i in icons: 85 | icon_definitions += " " * 4 86 | if len(i["hex"]) != 4: 87 | # Some icons has 5-digit unicode 88 | i["hex"] = "0" * (8 - len(i["hex"])) + i["hex"] 89 | icon_definitions += f'"{i["name"]}": "\\U{i["hex"].upper()}",\n' 90 | else: 91 | icon_definitions += f'"{i["name"]}": "\\u{i["hex"].upper()}",\n' 92 | icon_definitions += " " * 4 + f'"blank": " ",\n' # Add blank icon (space) 93 | icon_definitions += "}" 94 | return icon_definitions 95 | 96 | 97 | def export_icon_definitions(icon_definitions, version): 98 | with open(icon_definitions_path, "r") as f: 99 | icon_definitions_file = f.read() 100 | # Change md_icons list 101 | new_icon_definitions = re_icon_definitions.sub( 102 | icon_definitions.replace("\\", "\\\\"), icon_definitions_file, 1 103 | ) 104 | # Change version 105 | new_icon_definitions = re_version_in_file.sub( 106 | version, new_icon_definitions, 1 107 | ) 108 | with open(icon_definitions_path, "w") as f: 109 | f.write(new_icon_definitions) 110 | 111 | 112 | def main(): 113 | if url is not None: 114 | print(f"Downloading Material Design Icons from {url}") 115 | if download_file(url, "iconic-font.zip"): 116 | print("Archive downloaded") 117 | else: 118 | print("Could not download archive") 119 | else: 120 | print("URL is None. Do not download archive") 121 | if os.path.exists("iconic-font.zip"): 122 | unzip_archive("iconic-font.zip", temp_path) 123 | print("Unzip successful") 124 | os.remove("iconic-font.zip") 125 | if os.path.exists(temp_repo_path): 126 | shutil.copy2(temp_font_path, font_path) 127 | print("Font copied") 128 | icons, version = get_icons_list() 129 | print(f"Version {version}. {len(icons)} icons loaded") 130 | icon_definitions = make_icon_definitions(icons) 131 | export_icon_definitions(icon_definitions, version) 132 | print("File icon_definitions.py updated") 133 | shutil.rmtree(temp_path, ignore_errors=True) 134 | print( 135 | f'\nSuccessful. Commit message: "Update Iconic font (v{version})"' 136 | ) 137 | else: 138 | print(f"{temp_repo_path} not exists") 139 | 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /kivymd/uix/__init__.py: -------------------------------------------------------------------------------- 1 | from kivy.properties import BooleanProperty 2 | from kivy.uix.floatlayout import FloatLayout 3 | from kivy.uix.label import Label 4 | from kivy.uix.screenmanager import Screen 5 | 6 | from kivymd.uix.behaviors import SpecificBackgroundColorBehavior 7 | 8 | 9 | class MDAdaptiveWidget(SpecificBackgroundColorBehavior): 10 | adaptive_height = BooleanProperty(False) 11 | """ 12 | If `True`, the following properties will be applied to the widget: 13 | 14 | .. code-block:: kv 15 | 16 | size_hint_y: None 17 | height: self.minimum_height 18 | 19 | :attr:`adaptive_height` is an :class:`~kivy.properties.BooleanProperty` 20 | and defaults to `False`. 21 | """ 22 | 23 | adaptive_width = BooleanProperty(False) 24 | """ 25 | If `True`, the following properties will be applied to the widget: 26 | 27 | .. code-block:: kv 28 | 29 | size_hint_x: None 30 | width: self.minimum_width 31 | 32 | :attr:`adaptive_width` is an :class:`~kivy.properties.BooleanProperty` 33 | and defaults to `False`. 34 | """ 35 | 36 | adaptive_size = BooleanProperty(False) 37 | """ 38 | If `True`, the following properties will be applied to the widget: 39 | 40 | .. code-block:: kv 41 | 42 | size_hint: None, None 43 | size: self.minimum_size 44 | 45 | :attr:`adaptive_size` is an :class:`~kivy.properties.BooleanProperty` 46 | and defaults to `False`. 47 | """ 48 | 49 | def on_adaptive_height(self, instance, value): 50 | self.size_hint_y = None 51 | if issubclass(self.__class__, Label): 52 | self.bind( 53 | texture_size=lambda *x: self.setter("height")( 54 | self, self.texture_size[1] 55 | ) 56 | ) 57 | else: 58 | if not isinstance(self, (FloatLayout, Screen)): 59 | self.bind(minimum_height=self.setter("height")) 60 | 61 | def on_adaptive_width(self, instance, value): 62 | self.size_hint_x = None 63 | if issubclass(self.__class__, Label): 64 | self.bind( 65 | texture_size=lambda *x: self.setter("width")( 66 | self, self.texture_size[0] 67 | ) 68 | ) 69 | else: 70 | if not isinstance(self, (FloatLayout, Screen)): 71 | self.bind(minimum_width=self.setter("width")) 72 | 73 | def on_adaptive_size(self, instance, value): 74 | self.size_hint = (None, None) 75 | if issubclass(self.__class__, Label): 76 | self.text_size = (None, None) 77 | self.bind( 78 | texture_size=lambda *x: self.setter("size")( 79 | self, self.texture_size 80 | ) 81 | ) 82 | else: 83 | if not isinstance(self, (FloatLayout, Screen)): 84 | self.bind(minimum_size=self.setter("size")) 85 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors 3 | ========= 4 | 5 | Modules and classes implementing various behaviors for buttons etc. 6 | """ 7 | 8 | # flake8: NOQA 9 | from .hover_behavior import HoverBehavior # isort:skip 10 | from .backgroundcolor_behavior import ( 11 | BackgroundColorBehavior, 12 | SpecificBackgroundColorBehavior, 13 | ) 14 | from .elevation import ( 15 | CircularElevationBehavior, 16 | CommonElevationBehavior, 17 | FakeCircularElevationBehavior, 18 | FakeRectangularElevationBehavior, 19 | ObservableShadow, 20 | RectangularElevationBehavior, 21 | RoundedRectangularElevationBehavior, 22 | ) 23 | from .focus_behavior import FocusBehavior 24 | from .magic_behavior import MagicBehavior 25 | from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior 26 | from .touch_behavior import TouchBehavior 27 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/backgroundcolorbehavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors/Background Color 3 | ========================== 4 | 5 | .. note:: The following classes are intended for in-house use of the library. 6 | """ 7 | 8 | from kivy.lang import Builder 9 | from kivy.properties import BoundedNumericProperty, ReferenceListProperty 10 | from kivy.properties import OptionProperty, ListProperty 11 | from kivy.uix.widget import Widget 12 | from kivy.utils import get_color_from_hex 13 | 14 | from kivymd.color_definitions import palette, hue, text_colors 15 | 16 | Builder.load_string( 17 | """ 18 | 19 | canvas: 20 | Color: 21 | rgba: self.md_bg_color 22 | RoundedRectangle: 23 | size: self.size 24 | pos: self.pos 25 | radius: root.radius 26 | """ 27 | ) 28 | 29 | 30 | class BackgroundColorBehavior(Widget): 31 | r = BoundedNumericProperty(1.0, min=0.0, max=1.0) 32 | """The value of ``red`` in the ``rgba`` palette. 33 | 34 | :attr:`r` is an :class:`~kivy.properties.BoundedNumericProperty` 35 | and defaults to `1.0`. 36 | """ 37 | 38 | g = BoundedNumericProperty(1.0, min=0.0, max=1.0) 39 | """The value of ``green`` in the ``rgba`` palette. 40 | 41 | :attr:`g` is an :class:`~kivy.properties.BoundedNumericProperty` 42 | and defaults to `1.0`. 43 | """ 44 | 45 | b = BoundedNumericProperty(1.0, min=0.0, max=1.0) 46 | """The value of ``blue`` in the ``rgba`` palette. 47 | 48 | :attr:`b` is an :class:`~kivy.properties.BoundedNumericProperty` 49 | and defaults to `1.0`. 50 | """ 51 | 52 | a = BoundedNumericProperty(0.0, min=0.0, max=1.0) 53 | """The value of ``alpha channel`` in the ``rgba`` palette. 54 | 55 | :attr:`a` is an :class:`~kivy.properties.BoundedNumericProperty` 56 | and defaults to `0.0`. 57 | """ 58 | 59 | radius = ListProperty([0, 0, 0, 0]) 60 | """Canvas radius. 61 | 62 | .. code-block:: python 63 | 64 | # Top left corner slice. 65 | MDBoxLayout: 66 | md_bg_color: app.theme_cls.primary_color 67 | radius: [25, 0, 0, 0] 68 | 69 | :attr:`radius` is an :class:`~kivy.properties.ListProperty` 70 | and defaults to `[0, 0, 0, 0]`. 71 | """ 72 | 73 | md_bg_color = ReferenceListProperty(r, g, b, a) 74 | """The background color of the widget (:class:`~kivy.uix.widget.Widget`) 75 | that will be inherited from the :attr:`BackgroundColorBehavior` class. 76 | 77 | For example: 78 | 79 | .. code-block:: kv 80 | 81 | Widget: 82 | canvas: 83 | Color: 84 | rgba: 0, 1, 1, 1 85 | Rectangle: 86 | size: self.size 87 | pos: self.pos 88 | 89 | similar to code: 90 | 91 | .. code-block:: kv 92 | 93 | 94 | md_bg_color: 0, 1, 1, 1 95 | 96 | :attr:`md_bg_color` is an :class:`~kivy.properties.ReferenceListProperty` 97 | and defaults to :attr:`r`, :attr:`g`, :attr:`b`, :attr:`a`. 98 | """ 99 | 100 | 101 | class SpecificBackgroundColorBehavior(BackgroundColorBehavior): 102 | background_palette = OptionProperty( 103 | "Primary", options=["Primary", "Accent", *palette] 104 | ) 105 | """See :attr:`kivymd.color_definitions.palette`. 106 | 107 | :attr:`background_palette` is an :class:`~kivy.properties.OptionProperty` 108 | and defaults to `'Primary'`. 109 | """ 110 | 111 | background_hue = OptionProperty("500", options=hue) 112 | """See :attr:`kivymd.color_definitions.hue`. 113 | 114 | :attr:`background_hue` is an :class:`~kivy.properties.OptionProperty` 115 | and defaults to `'500'`. 116 | """ 117 | 118 | specific_text_color = ListProperty([0, 0, 0, 0.87]) 119 | """:attr:`specific_text_color` is an :class:`~kivy.properties.ListProperty` 120 | and defaults to `[0, 0, 0, 0.87]`. 121 | """ 122 | 123 | specific_secondary_text_color = ListProperty([0, 0, 0, 0.87]) 124 | """:attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ListProperty` 125 | and defaults to `[0, 0, 0, 0.87]`. 126 | """ 127 | 128 | def _update_specific_text_color(self, instance, value): 129 | if hasattr(self, "theme_cls"): 130 | palette = { 131 | "Primary": self.theme_cls.primary_palette, 132 | "Accent": self.theme_cls.accent_palette, 133 | }.get(self.background_palette, self.background_palette) 134 | else: 135 | palette = {"Primary": "Blue", "Accent": "Amber"}.get( 136 | self.background_palette, self.background_palette 137 | ) 138 | color = get_color_from_hex(text_colors[palette][self.background_hue]) 139 | secondary_color = color[:] 140 | # Check for black text (need to adjust opacity) 141 | if (color[0] + color[1] + color[2]) == 0: 142 | color[3] = 0.87 143 | secondary_color[3] = 0.54 144 | else: 145 | secondary_color[3] = 0.7 146 | self.specific_text_color = color 147 | self.specific_secondary_text_color = secondary_color 148 | 149 | def __init__(self, **kwargs): 150 | super().__init__(**kwargs) 151 | if hasattr(self, "theme_cls"): 152 | self.theme_cls.bind( 153 | primary_palette=self._update_specific_text_color 154 | ) 155 | self.theme_cls.bind(accent_palette=self._update_specific_text_color) 156 | self.theme_cls.bind(theme_style=self._update_specific_text_color) 157 | self.bind(background_hue=self._update_specific_text_color) 158 | self.bind(background_palette=self._update_specific_text_color) 159 | self._update_specific_text_color(None, None) 160 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/focus_behavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors/Focus 3 | =============== 4 | 5 | .. rubric:: Changing the background color when the mouse is on the widget. 6 | 7 | To apply focus behavior, you must create a new class that is inherited from the 8 | widget to which you apply the behavior and from the :class:`FocusBehavior` class. 9 | 10 | Usage 11 | ----- 12 | 13 | .. code-block:: python 14 | 15 | from kivy.lang import Builder 16 | 17 | from kivymd.app import MDApp 18 | from kivymd.uix.behaviors import RectangularElevationBehavior, FocusBehavior 19 | from kivymd.uix.boxlayout import MDBoxLayout 20 | 21 | KV = ''' 22 | MDScreen: 23 | md_bg_color: 1, 1, 1, 1 24 | 25 | FocusWidget: 26 | size_hint: .5, .3 27 | pos_hint: {"center_x": .5, "center_y": .5} 28 | md_bg_color: app.theme_cls.bg_light 29 | 30 | MDLabel: 31 | text: "Label" 32 | theme_text_color: "Primary" 33 | pos_hint: {"center_y": .5} 34 | halign: "center" 35 | ''' 36 | 37 | 38 | class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior): 39 | pass 40 | 41 | 42 | class Test(MDApp): 43 | def build(self): 44 | self.theme_cls.theme_style = "Dark" 45 | return Builder.load_string(KV) 46 | 47 | 48 | Test().run() 49 | 50 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-widget.gif 51 | :align: center 52 | 53 | Color change at focus/defocus 54 | 55 | .. code-block:: kv 56 | 57 | FocusWidget: 58 | focus_color: 1, 0, 1, 1 59 | unfocus_color: 0, 0, 1, 1 60 | 61 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-defocus-color.gif 62 | :align: center 63 | """ 64 | 65 | __all__ = ("FocusBehavior",) 66 | 67 | from kivy.app import App 68 | from kivy.properties import BooleanProperty, ColorProperty 69 | from kivy.uix.behaviors import ButtonBehavior 70 | 71 | from kivymd.uix.behaviors import HoverBehavior 72 | 73 | 74 | class FocusBehavior(HoverBehavior, ButtonBehavior): 75 | 76 | focus_behavior = BooleanProperty(True) 77 | """ 78 | Using focus when hovering over a widget. 79 | 80 | :attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty` 81 | and defaults to `False`. 82 | """ 83 | 84 | focus_color = ColorProperty(None) 85 | """ 86 | The color of the widget when the mouse enters the bbox of the widget. 87 | 88 | :attr:`focus_color` is a :class:`~kivy.properties.ColorProperty` 89 | and defaults to `None`. 90 | """ 91 | 92 | unfocus_color = ColorProperty(None) 93 | """ 94 | The color of the widget when the mouse exits the bbox widget. 95 | 96 | :attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty` 97 | and defaults to `None`. 98 | """ 99 | 100 | def on_enter(self): 101 | """Called when mouse enter the bbox of the widget.""" 102 | 103 | if hasattr(self, "md_bg_color") and self.focus_behavior: 104 | if hasattr(self, "theme_cls") and not self.focus_color: 105 | self.md_bg_color = self.theme_cls.bg_normal 106 | else: 107 | if not self.focus_color: 108 | self.md_bg_color = App.get_running_app().theme_cls.bg_normal 109 | else: 110 | self.md_bg_color = self.focus_color 111 | 112 | def on_leave(self): 113 | """Called when the mouse exit the widget.""" 114 | 115 | if hasattr(self, "md_bg_color") and self.focus_behavior: 116 | if hasattr(self, "theme_cls") and not self.unfocus_color: 117 | self.md_bg_color = self.theme_cls.bg_light 118 | else: 119 | if not self.unfocus_color: 120 | self.md_bg_color = App.get_running_app().theme_cls.bg_light 121 | else: 122 | self.md_bg_color = self.unfocus_color 123 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/magic_behavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors/Magic 3 | =============== 4 | 5 | .. rubric:: Magical effects for buttons. 6 | 7 | .. warning:: Magic effects do not work correctly with `KivyMD` buttons! 8 | 9 | To apply magic effects, you must create a new class that is inherited from the 10 | widget to which you apply the effect and from the :attr:`MagicBehavior` class. 11 | 12 | In `KV file`: 13 | 14 | .. code-block:: kv 15 | 16 | 17 | 18 | In `python file`: 19 | 20 | .. code-block:: python 21 | 22 | class MagicButton(MagicBehavior, MDRectangleFlatButton): 23 | pass 24 | 25 | .. rubric:: The :attr:`MagicBehavior` class provides five effects: 26 | 27 | - :attr:`MagicBehavior.wobble` 28 | - :attr:`MagicBehavior.grow` 29 | - :attr:`MagicBehavior.shake` 30 | - :attr:`MagicBehavior.twist` 31 | - :attr:`MagicBehavior.shrink` 32 | 33 | Example: 34 | 35 | .. code-block:: python 36 | 37 | from kivymd.app import MDApp 38 | from kivy.lang import Builder 39 | 40 | KV = ''' 41 | #:import MagicBehavior kivymd.uix.behaviors.MagicBehavior 42 | 43 | 44 | 45 | 46 | 47 | FloatLayout: 48 | 49 | MagicButton: 50 | text: "WOBBLE EFFECT" 51 | on_release: self.wobble() 52 | pos_hint: {"center_x": .5, "center_y": .3} 53 | 54 | MagicButton: 55 | text: "GROW EFFECT" 56 | on_release: self.grow() 57 | pos_hint: {"center_x": .5, "center_y": .4} 58 | 59 | MagicButton: 60 | text: "SHAKE EFFECT" 61 | on_release: self.shake() 62 | pos_hint: {"center_x": .5, "center_y": .5} 63 | 64 | MagicButton: 65 | text: "TWIST EFFECT" 66 | on_release: self.twist() 67 | pos_hint: {"center_x": .5, "center_y": .6} 68 | 69 | MagicButton: 70 | text: "SHRINK EFFECT" 71 | on_release: self.shrink() 72 | pos_hint: {"center_x": .5, "center_y": .7} 73 | ''' 74 | 75 | 76 | class Example(MDApp): 77 | def build(self): 78 | return Builder.load_string(KV) 79 | 80 | 81 | Example().run() 82 | 83 | 84 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/magic-button.gif 85 | :width: 250 px 86 | :align: center 87 | """ 88 | 89 | __all__ = ("MagicBehavior",) 90 | 91 | from kivy.animation import Animation 92 | from kivy.lang import Builder 93 | from kivy.properties import NumericProperty 94 | 95 | Builder.load_string( 96 | """ 97 | 98 | translate_x: 0 99 | translate_y: 0 100 | scale_x: 1 101 | scale_y: 1 102 | rotate: 0 103 | 104 | canvas.before: 105 | PushMatrix 106 | Translate: 107 | x: self.translate_x or 0 108 | y: self.translate_y or 0 109 | Rotate: 110 | origin: self.center 111 | angle: self.rotate or 0 112 | Scale: 113 | origin: self.center 114 | x: self.scale_x or 1 115 | y: self.scale_y or 1 116 | canvas.after: 117 | PopMatrix 118 | """ 119 | ) 120 | 121 | 122 | class MagicBehavior: 123 | 124 | magic_speed = NumericProperty(1) 125 | """ 126 | Animation playback speed. 127 | 128 | :attr:`magic_speed` is a :class:`~kivy.properties.NumericProperty` 129 | and defaults to `1`. 130 | """ 131 | 132 | def grow(self): 133 | """Grow effect animation.""" 134 | 135 | ( 136 | Animation( 137 | scale_x=1.2, 138 | scale_y=1.2, 139 | t="out_quad", 140 | d=0.03 / self.magic_speed, 141 | ) 142 | + Animation( 143 | scale_x=1, scale_y=1, t="out_elastic", d=0.4 / self.magic_speed 144 | ) 145 | ).start(self) 146 | 147 | def shake(self): 148 | """Shake effect animation.""" 149 | 150 | ( 151 | Animation(translate_x=50, t="out_quad", d=0.02 / self.magic_speed) 152 | + Animation( 153 | translate_x=0, t="out_elastic", d=0.5 / self.magic_speed 154 | ) 155 | ).start(self) 156 | 157 | def wobble(self): 158 | """Wobble effect animation.""" 159 | 160 | ( 161 | ( 162 | Animation(scale_y=0.7, t="out_quad", d=0.03 / self.magic_speed) 163 | & Animation( 164 | scale_x=1.4, t="out_quad", d=0.03 / self.magic_speed 165 | ) 166 | ) 167 | + ( 168 | Animation(scale_y=1, t="out_elastic", d=0.5 / self.magic_speed) 169 | & Animation( 170 | scale_x=1, t="out_elastic", d=0.4 / self.magic_speed 171 | ) 172 | ) 173 | ).start(self) 174 | 175 | def twist(self): 176 | """Twist effect animation.""" 177 | 178 | ( 179 | Animation(rotate=25, t="out_quad", d=0.05 / self.magic_speed) 180 | + Animation(rotate=0, t="out_elastic", d=0.5 / self.magic_speed) 181 | ).start(self) 182 | 183 | def shrink(self): 184 | """Shrink effect animation.""" 185 | 186 | Animation( 187 | scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1 / self.magic_speed 188 | ).start(self) 189 | 190 | def on_touch_up(self, *args): 191 | Animation.stop_all(self) 192 | return super().on_touch_up(*args) 193 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/toggle_behavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors/ToggleButton 3 | ====================== 4 | 5 | This behavior must always be inherited after the button's Widget class since it 6 | works with the inherited properties of the button class. 7 | 8 | example: 9 | 10 | .. code-block:: python 11 | 12 | class MyToggleButtonWidget(MDFlatButton, MDToggleButton): 13 | # [...] 14 | pass 15 | 16 | 17 | .. code-block:: python 18 | 19 | from kivy.lang import Builder 20 | 21 | from kivymd.app import MDApp 22 | from kivymd.uix.behaviors.toggle_behavior import MDToggleButton 23 | from kivymd.uix.button import MDRectangleFlatButton 24 | 25 | KV = ''' 26 | Screen: 27 | 28 | MDBoxLayout: 29 | adaptive_size: True 30 | pos_hint: {"center_x": .5, "center_y": .5} 31 | 32 | MyToggleButton: 33 | text: "Show ads" 34 | group: "x" 35 | 36 | MyToggleButton: 37 | text: "Do not show ads" 38 | group: "x" 39 | 40 | MyToggleButton: 41 | text: "Does not matter" 42 | group: "x" 43 | ''' 44 | 45 | 46 | class MyToggleButton(MDRectangleFlatButton, MDToggleButton): 47 | def __init__(self, **kwargs): 48 | super().__init__(**kwargs) 49 | self.background_down = self.theme_cls.primary_light 50 | 51 | 52 | class Test(MDApp): 53 | def build(self): 54 | return Builder.load_string(KV) 55 | 56 | 57 | Test().run() 58 | 59 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif 60 | :align: center 61 | 62 | .. code-block:: python 63 | 64 | class MyToggleButton(MDFillRoundFlatButton, MDToggleButton): 65 | def __init__(self, **kwargs): 66 | self.background_down = MDApp.get_running_app().theme_cls.primary_dark 67 | super().__init__(**kwargs) 68 | 69 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif 70 | :align: center 71 | 72 | You can inherit the ``MyToggleButton`` class only from the following classes 73 | ---------------------------------------------------------------------------- 74 | 75 | - :class:`~kivymd.uix.button.MDRaisedButton` 76 | - :class:`~kivymd.uix.button.MDFlatButton` 77 | - :class:`~kivymd.uix.button.MDRectangleFlatButton` 78 | - :class:`~kivymd.uix.button.MDRectangleFlatIconButton` 79 | - :class:`~kivymd.uix.button.MDRoundFlatButton` 80 | - :class:`~kivymd.uix.button.MDRoundFlatIconButton` 81 | - :class:`~kivymd.uix.button.MDFillRoundFlatButton` 82 | - :class:`~kivymd.uix.button.MDFillRoundFlatIconButton` 83 | """ 84 | 85 | __all__ = ("MDToggleButton",) 86 | 87 | from kivy.properties import BooleanProperty, ColorProperty 88 | from kivy.uix.behaviors import ToggleButtonBehavior 89 | 90 | from kivymd.uix.button import ( 91 | MDFillRoundFlatButton, 92 | MDFillRoundFlatIconButton, 93 | MDFlatButton, 94 | MDRaisedButton, 95 | MDRectangleFlatButton, 96 | MDRectangleFlatIconButton, 97 | MDRoundFlatButton, 98 | MDRoundFlatIconButton, 99 | ) 100 | 101 | 102 | class MDToggleButton(ToggleButtonBehavior): 103 | background_normal = ColorProperty(None) 104 | """ 105 | Color of the button in ``rgba`` format for the 'normal' state. 106 | 107 | :attr:`background_normal` is a :class:`~kivy.properties.ColorProperty` 108 | and is defaults to `None`. 109 | """ 110 | 111 | background_down = ColorProperty(None) 112 | """ 113 | Color of the button in ``rgba`` format for the 'down' state. 114 | 115 | :attr:`background_down` is a :class:`~kivy.properties.ColorProperty` 116 | and is defaults to `None`. 117 | """ 118 | 119 | font_color_normal = ColorProperty(None) 120 | """ 121 | Color of the font's button in ``rgba`` format for the 'normal' state. 122 | 123 | :attr:`font_color_normal` is a :class:`~kivy.properties.ColorProperty` 124 | and is defaults to `None`. 125 | """ 126 | 127 | font_color_down = ColorProperty([1, 1, 1, 1]) 128 | """ 129 | Color of the font's button in ``rgba`` format for the 'down' state. 130 | 131 | :attr:`font_color_down` is a :class:`~kivy.properties.ColorProperty` 132 | and is defaults to `[1, 1, 1, 1]`. 133 | """ 134 | 135 | __is_filled = BooleanProperty(False) 136 | 137 | def __init__(self, **kwargs): 138 | super().__init__(**kwargs) 139 | classinfo = ( 140 | MDRaisedButton, 141 | MDFlatButton, 142 | MDRectangleFlatButton, 143 | MDRectangleFlatIconButton, 144 | MDRoundFlatButton, 145 | MDRoundFlatIconButton, 146 | MDFillRoundFlatButton, 147 | MDFillRoundFlatIconButton, 148 | ) 149 | # Do the object inherited from the "supported" buttons? 150 | if not issubclass(self.__class__, classinfo): 151 | raise ValueError( 152 | f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}" 153 | ) 154 | if ( 155 | not self.background_normal 156 | ): # This means that if the value == [] or None will return True. 157 | # If the object inherits from buttons with background: 158 | if isinstance( 159 | self, 160 | ( 161 | MDRaisedButton, 162 | MDFillRoundFlatButton, 163 | MDFillRoundFlatIconButton, 164 | ), 165 | ): 166 | self.__is_filled = True 167 | self.background_normal = self.theme_cls.primary_color 168 | # If not the background_normal must be the same as the inherited one: 169 | else: 170 | self.background_normal = self.md_bg_color[:] 171 | # If no background_down is setted: 172 | if ( 173 | not self.background_down 174 | ): # This means that if the value == [] or None will return True. 175 | self.background_down = ( 176 | self.theme_cls.primary_dark 177 | ) # get the primary_color dark from theme_cls 178 | if not self.font_color_normal: 179 | self.font_color_normal = self.theme_cls.primary_color 180 | # Alternative to bind the function to the property. 181 | # self.bind(state=self._update_bg) 182 | self.fbind("state", self._update_bg) 183 | 184 | def _update_bg(self, ins, val): 185 | """Updates the color of the background.""" 186 | 187 | if val == "down": 188 | self.md_bg_color = self.background_down 189 | if ( 190 | self.__is_filled is False 191 | ): # If the background is transparent, and the button it toggled, 192 | # the font color must be withe [1, 1, 1, 1]. 193 | self.text_color = self.font_color_down 194 | if self.group: 195 | self._release_group(self) 196 | else: 197 | self.md_bg_color = self.background_normal 198 | if ( 199 | self.__is_filled is False 200 | ): # If the background is transparent, the font color must be the 201 | # primary color. 202 | self.text_color = self.font_color_normal 203 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/touch_behavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors/Touch 3 | =============== 4 | 5 | .. rubric:: Provides easy access to events. 6 | 7 | The following events are available: 8 | 9 | - on_long_touch 10 | - on_double_tap 11 | - on_triple_tap 12 | 13 | Usage 14 | ----- 15 | 16 | .. code-block:: python 17 | 18 | from kivy.lang import Builder 19 | 20 | from kivymd.app import MDApp 21 | from kivymd.uix.behaviors import TouchBehavior 22 | from kivymd.uix.button import MDRaisedButton 23 | 24 | KV = ''' 25 | Screen: 26 | 27 | MyButton: 28 | text: "PRESS ME" 29 | pos_hint: {"center_x": .5, "center_y": .5} 30 | ''' 31 | 32 | 33 | class MyButton(MDRaisedButton, TouchBehavior): 34 | def on_long_touch(self, *args): 35 | print(" event") 36 | 37 | def on_double_tap(self, *args): 38 | print(" event") 39 | 40 | def on_triple_tap(self, *args): 41 | print(" event") 42 | 43 | 44 | class MainApp(MDApp): 45 | def build(self): 46 | return Builder.load_string(KV) 47 | 48 | 49 | MainApp().run() 50 | """ 51 | 52 | __all__ = ("TouchBehavior",) 53 | 54 | from functools import partial 55 | 56 | from kivy.clock import Clock 57 | from kivy.properties import NumericProperty 58 | 59 | 60 | class TouchBehavior: 61 | duration_long_touch = NumericProperty(0.4) 62 | """ 63 | Time for a long touch. 64 | 65 | :attr:`duration_long_touch` is an :class:`~kivy.properties.NumericProperty` 66 | and defaults to `0.4`. 67 | """ 68 | 69 | def __init__(self, **kwargs): 70 | super().__init__(**kwargs) 71 | self.bind( 72 | on_touch_down=self.create_clock, on_touch_up=self.delete_clock 73 | ) 74 | 75 | def create_clock(self, widget, touch, *args): 76 | if self.collide_point(touch.x, touch.y): 77 | callback = partial(self.on_long_touch, touch) 78 | Clock.schedule_once(callback, self.duration_long_touch) 79 | touch.ud["event"] = callback 80 | 81 | def delete_clock(self, widget, touch, *args): 82 | if self.collide_point(touch.x, touch.y): 83 | try: 84 | Clock.unschedule(touch.ud["event"]) 85 | except KeyError: 86 | pass 87 | 88 | if touch.is_double_tap: 89 | self.on_double_tap(touch, *args) 90 | if touch.is_triple_tap: 91 | self.on_triple_tap(touch, *args) 92 | 93 | def on_long_touch(self, touch, *args): 94 | """Called when the widget is pressed for a long time.""" 95 | 96 | def on_double_tap(self, touch, *args): 97 | """Called by double clicking on the widget.""" 98 | 99 | def on_triple_tap(self, touch, *args): 100 | """Called by triple clicking on the widget.""" 101 | -------------------------------------------------------------------------------- /kivymd/uix/boxlayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Box Layout 3 | ===================== 4 | 5 | :class:`~kivy.uix.boxlayout.BoxLayout` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | BoxLayout 9 | --------- 10 | 11 | .. code-block:: 12 | 13 | BoxLayout: 14 | size_hint_y: None 15 | height: self.minimum_height 16 | 17 | canvas: 18 | Color: 19 | rgba: app.theme_cls.primary_color 20 | Rectangle: 21 | pos: self.pos 22 | size: self.size 23 | 24 | MDBoxLayout 25 | ----------- 26 | 27 | .. code-block:: 28 | 29 | MDBoxLayout: 30 | adaptive_height: True 31 | md_bg_color: app.theme_cls.primary_color 32 | 33 | Available options are: 34 | ---------------------- 35 | 36 | - adaptive_height_ 37 | - adaptive_width_ 38 | - adaptive_size_ 39 | 40 | .. adaptive_height: 41 | adaptive_height 42 | --------------- 43 | 44 | .. code-block:: kv 45 | 46 | adaptive_height: True 47 | 48 | Equivalent 49 | 50 | .. code-block:: kv 51 | 52 | size_hint_y: None 53 | height: self.minimum_height 54 | 55 | .. adaptive_width: 56 | adaptive_width 57 | -------------- 58 | 59 | .. code-block:: kv 60 | 61 | adaptive_width: True 62 | 63 | Equivalent 64 | 65 | .. code-block:: kv 66 | 67 | size_hint_x: None 68 | height: self.minimum_width 69 | 70 | .. adaptive_size: 71 | adaptive_size 72 | ------------- 73 | 74 | .. code-block:: kv 75 | 76 | adaptive_size: True 77 | 78 | Equivalent 79 | 80 | .. code-block:: kv 81 | 82 | size_hint: None, None 83 | size: self.minimum_size 84 | """ 85 | 86 | from kivy.uix.boxlayout import BoxLayout 87 | 88 | from kivymd.uix import MDAdaptiveWidget 89 | 90 | 91 | class MDBoxLayout(BoxLayout, MDAdaptiveWidget): 92 | pass 93 | -------------------------------------------------------------------------------- /kivymd/uix/circularlayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/CircularLayout 3 | ========================= 4 | 5 | CircularLayout is a special layout that places widgets around a circle. 6 | 7 | MDCircularLayout 8 | ---------------- 9 | 10 | .. rubric:: Usage 11 | 12 | .. code-block:: 13 | 14 | from kivy.lang.builder import Builder 15 | from kivy.uix.label import Label 16 | 17 | from kivymd.app import MDApp 18 | 19 | kv = ''' 20 | Screen: 21 | MDCircularLayout: 22 | id: container 23 | pos_hint: {"center_x": .5, "center_y": .5} 24 | row_spacing: min(self.size)*0.1 25 | ''' 26 | 27 | 28 | class Main(MDApp): 29 | def build(self): 30 | return Builder.load_string(kv) 31 | 32 | def on_start(self): 33 | for x in range(1, 49): 34 | self.root.ids.container.add_widget( 35 | Label(text=f"{x}", color=[0, 0, 0, 1]) 36 | ) 37 | 38 | 39 | Main().run() 40 | 41 | """ 42 | 43 | __all__ = ("MDCircularLayout",) 44 | 45 | from math import atan2, cos, degrees, radians, sin 46 | 47 | from kivy.properties import BooleanProperty, NumericProperty 48 | 49 | from kivymd.uix.floatlayout import MDFloatLayout 50 | 51 | 52 | class MDCircularLayout(MDFloatLayout): 53 | 54 | degree_spacing = NumericProperty(30) 55 | """ 56 | The space between children in degree. 57 | 58 | :attr:`degree_spacing` is an :class:`~kivy.properties.NumericProperty` 59 | and defaults to `30`. 60 | """ 61 | 62 | circular_radius = NumericProperty(None, allownone=True) 63 | """ 64 | Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified. 65 | 66 | :attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty` 67 | and defaults to `None`. 68 | """ 69 | 70 | start_from = NumericProperty(60) 71 | """ 72 | The positon of first child in degree. 73 | 74 | :attr:`start_from` is an :class:`~kivy.properties.NumericProperty` 75 | and defaults to `60`. 76 | """ 77 | 78 | max_degree = NumericProperty(360) 79 | """ 80 | Maximum range in degree allowed for each row of widgets before jumping to the next row. 81 | 82 | :attr:`max_degree` is an :class:`~kivy.properties.NumericProperty` 83 | and defaults to `360`. 84 | """ 85 | 86 | circular_padding = NumericProperty("25dp") 87 | """ 88 | Padding between outer widgets and the edge of the biggest circle. 89 | 90 | :attr:`circular_padding` is an :class:`~kivy.properties.NumericProperty` 91 | and defaults to `25dp`. 92 | """ 93 | 94 | row_spacing = NumericProperty("50dp") 95 | """ 96 | Space between each row of widget. 97 | 98 | :attr:`row_spacing` is an :class:`~kivy.properties.NumericProperty` 99 | and defaults to `50dp`. 100 | """ 101 | 102 | clockwise = BooleanProperty(True) 103 | """ 104 | Direction of widgets in circular direction. 105 | 106 | :attr:`clockwise` is an :class:`~kivy.properties.BooleanProperty` 107 | and defaults to `True`. 108 | """ 109 | 110 | def __init__(self, **kwargs): 111 | super().__init__(**kwargs) 112 | self.bind( 113 | row_spacing=self._update_layout, 114 | ) 115 | 116 | def _update_layout(self, *args): 117 | for index, child in enumerate(reversed(self.children)): 118 | pos = self._point_on_circle( 119 | self._calculate_radius(index), 120 | self._calculate_degree(index), 121 | ) 122 | child.center = pos 123 | 124 | def do_layout(self, *largs, **kwargs): 125 | self._update_layout() 126 | return super().do_layout(*largs, **kwargs) 127 | 128 | def _max_per_row(self): 129 | return int(self.max_degree / self.degree_spacing) 130 | 131 | def _calculate_radius(self, index): 132 | """ 133 | calculates the radius for given index 134 | 135 | """ 136 | 137 | idx = int(index / self._max_per_row()) 138 | 139 | if not self.circular_radius: 140 | init_radius = ( 141 | min([self.width / 2, self.height / 2]) - self.circular_padding 142 | ) 143 | else: 144 | init_radius = self.circular_radius 145 | 146 | if idx != 0: 147 | space = self.row_spacing * idx 148 | init_radius -= space 149 | 150 | return init_radius 151 | 152 | def _calculate_degree(self, index): 153 | """ calculates the angle for given index""" 154 | if self.clockwise: 155 | degree = self.start_from - index * self.degree_spacing 156 | else: 157 | degree = self.start_from + index * self.degree_spacing 158 | 159 | return degree 160 | 161 | def remove_widget(self, widget, **kwargs): 162 | super().remove_widget(widget, **kwargs) 163 | self._update_layout() 164 | 165 | def _point_on_circle(self, radius, degree): 166 | angle = radians(degree) 167 | center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] 168 | x = center[0] + (radius * cos(angle)) 169 | y = center[1] + (radius * sin(angle)) 170 | return [x, y] 171 | 172 | def get_angle(self, pos): 173 | """ 174 | Returns the angle of given pos 175 | """ 176 | 177 | center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] 178 | (dx, dy) = (center[0] - pos[0], center[1] - pos[1]) 179 | angle = degrees(atan2(float(dy), float(dx))) 180 | angle += 180 181 | return angle 182 | 183 | 184 | if __name__ == "__main__": 185 | from kivy.lang.builder import Builder 186 | from kivy.uix.label import Label 187 | 188 | from kivymd.app import MDApp 189 | 190 | kv = """ 191 | Screen: 192 | MDCircularLayout: 193 | id: container 194 | pos_hint: {"center_x": .5, "center_y": .5} 195 | row_spacing: min(self.size)*0.1 196 | """ 197 | 198 | class Main(MDApp): 199 | def build(self): 200 | return Builder.load_string(kv) 201 | 202 | def on_start(self): 203 | for x in range(1, 49): 204 | self.root.ids.container.add_widget( 205 | Label(text=f"{x}", color=[0, 0, 0, 1]) 206 | ) 207 | 208 | Main().run() 209 | -------------------------------------------------------------------------------- /kivymd/uix/dropdownitem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Dropdown Item 3 | ======================== 4 | 5 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dropdown-item.png 6 | :align: center 7 | 8 | Usage 9 | ----- 10 | 11 | .. code-block:: python 12 | 13 | from kivy.lang import Builder 14 | 15 | from kivymd.app import MDApp 16 | 17 | KV = ''' 18 | Screen 19 | 20 | MDDropDownItem: 21 | id: drop_item 22 | pos_hint: {'center_x': .5, 'center_y': .5} 23 | text: 'Item' 24 | on_release: self.set_item("New Item") 25 | ''' 26 | 27 | 28 | class Test(MDApp): 29 | def __init__(self, **kwargs): 30 | super().__init__(**kwargs) 31 | self.screen = Builder.load_string(KV) 32 | 33 | def build(self): 34 | return self.screen 35 | 36 | 37 | Test().run() 38 | 39 | .. seealso:: 40 | 41 | `Work with the class MDDropdownMenu see here `_ 42 | """ 43 | 44 | __all__ = ("MDDropDownItem",) 45 | 46 | from kivy.lang import Builder 47 | from kivy.properties import NumericProperty, StringProperty 48 | from kivy.uix.behaviors import ButtonBehavior 49 | from kivy.uix.widget import Widget 50 | 51 | from kivymd.theming import ThemableBehavior 52 | from kivymd.uix.behaviors import FakeRectangularElevationBehavior 53 | from kivymd.uix.boxlayout import MDBoxLayout 54 | 55 | Builder.load_string( 56 | """ 57 | <_Triangle>: 58 | canvas: 59 | Color: 60 | rgba: root.theme_cls.text_color 61 | Triangle: 62 | points: 63 | [ \ 64 | self.right-dp(14), self.y+dp(7), \ 65 | self.right-dp(7), self.y+dp(7), \ 66 | self.right-dp(7), self.y+dp(14) \ 67 | ] 68 | 69 | 70 | 71 | orientation: "vertical" 72 | adaptive_size: True 73 | spacing: "5dp" 74 | padding: "5dp", "5dp", "5dp", 0 75 | 76 | MDBoxLayout: 77 | adaptive_size: True 78 | spacing: "10dp" 79 | 80 | Label: 81 | id: label_item 82 | size_hint: None, None 83 | size: self.texture_size 84 | color: root.theme_cls.text_color 85 | font_size: root.font_size 86 | 87 | 88 | _Triangle: 89 | size_hint: None, None 90 | size: "20dp", "20dp" 91 | 92 | MDSeparator: 93 | """ 94 | ) 95 | 96 | 97 | class _Triangle(ThemableBehavior, Widget): 98 | pass 99 | 100 | 101 | class MDDropDownItem( 102 | ThemableBehavior, 103 | FakeRectangularElevationBehavior, 104 | ButtonBehavior, 105 | MDBoxLayout, 106 | ): 107 | text = StringProperty() 108 | """ 109 | Text item. 110 | 111 | :attr:`text` is a :class:`~kivy.properties.StringProperty` 112 | and defaults to `''`. 113 | """ 114 | 115 | current_item = StringProperty() 116 | """ 117 | Current name item. 118 | 119 | :attr:`current_item` is a :class:`~kivy.properties.StringProperty` 120 | and defaults to `''`. 121 | """ 122 | 123 | font_size = NumericProperty("16sp") 124 | """ 125 | Item font size. 126 | 127 | :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` 128 | and defaults to `'16sp'`. 129 | """ 130 | 131 | def on_text(self, instance, value): 132 | self.ids.label_item.text = value 133 | 134 | def set_item(self, name_item): 135 | """Sets new text for an item.""" 136 | 137 | self.ids.label_item.text = name_item 138 | self.current_item = name_item 139 | -------------------------------------------------------------------------------- /kivymd/uix/floatlayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Float Layout 3 | ======================= 4 | 5 | :class:`~kivy.uix.floatlayout.FloatLayout` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | FloatLayout 9 | ----------- 10 | 11 | .. code-block:: 12 | 13 | FloatLayout: 14 | canvas: 15 | Color: 16 | rgba: app.theme_cls.primary_color 17 | RoundedRectangle: 18 | pos: self.pos 19 | size: self.size 20 | radius: [25, 0, 0, 0] 21 | 22 | MDFloatLayout 23 | ------------- 24 | 25 | .. code-block:: 26 | 27 | MDFloatLayout: 28 | radius: [25, 0, 0, 0] 29 | md_bg_color: app.theme_cls.primary_color 30 | 31 | .. Warning:: For a :class:`~kivy.uix.floatlayout.FloatLayout`, the 32 | ``minimum_size`` attributes are always 0, so you cannot use 33 | ``adaptive_size`` and related options. 34 | """ 35 | 36 | from kivy.uix.floatlayout import FloatLayout 37 | 38 | from kivymd.uix import MDAdaptiveWidget 39 | 40 | 41 | class MDFloatLayout(FloatLayout, MDAdaptiveWidget): 42 | pass 43 | -------------------------------------------------------------------------------- /kivymd/uix/gridlayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Grid Layout 3 | ====================== 4 | 5 | :class:`~kivy.uix.gridlayout.GridLayout` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | GridLayout 9 | ---------- 10 | 11 | .. code-block:: 12 | 13 | GridLayout: 14 | size_hint_y: None 15 | height: self.minimum_height 16 | 17 | canvas: 18 | Color: 19 | rgba: app.theme_cls.primary_color 20 | Rectangle: 21 | pos: self.pos 22 | size: self.size 23 | 24 | MDGridLayout 25 | ------------ 26 | 27 | .. code-block:: 28 | 29 | MDGridLayout: 30 | adaptive_height: True 31 | md_bg_color: app.theme_cls.primary_color 32 | 33 | Available options are: 34 | ---------------------- 35 | 36 | - adaptive_height_ 37 | - adaptive_width_ 38 | - adaptive_size_ 39 | 40 | .. adaptive_height: 41 | adaptive_height 42 | --------------- 43 | 44 | .. code-block:: kv 45 | 46 | adaptive_height: True 47 | 48 | Equivalent 49 | 50 | .. code-block:: kv 51 | 52 | size_hint_y: None 53 | height: self.minimum_height 54 | 55 | .. adaptive_width: 56 | adaptive_width 57 | -------------- 58 | 59 | .. code-block:: kv 60 | 61 | adaptive_width: True 62 | 63 | Equivalent 64 | 65 | .. code-block:: kv 66 | 67 | size_hint_x: None 68 | height: self.minimum_width 69 | 70 | .. adaptive_size: 71 | adaptive_size 72 | ------------- 73 | 74 | .. code-block:: kv 75 | 76 | adaptive_size: True 77 | 78 | Equivalent 79 | 80 | .. code-block:: kv 81 | 82 | size_hint: None, None 83 | size: self.minimum_size 84 | """ 85 | 86 | from kivy.uix.gridlayout import GridLayout 87 | 88 | from kivymd.uix import MDAdaptiveWidget 89 | 90 | 91 | class MDGridLayout(GridLayout, MDAdaptiveWidget): 92 | pass 93 | -------------------------------------------------------------------------------- /kivymd/uix/label.py: -------------------------------------------------------------------------------- 1 | __all__ = ("MDLabel", "MDIcon") 2 | 3 | from kivy.clock import Clock 4 | from kivy.lang import Builder 5 | from kivy.metrics import sp 6 | from kivy.properties import ( 7 | AliasProperty, 8 | BooleanProperty, 9 | ColorProperty, 10 | OptionProperty, 11 | StringProperty, 12 | ) 13 | from kivy.uix.label import Label 14 | 15 | from kivymd.theming import ThemableBehavior 16 | from kivymd.theming_dynamic_text import get_contrast_text_color 17 | from kivymd.uix import MDAdaptiveWidget 18 | 19 | __MDLabel_colors__ = { 20 | "Primary": "text_color", 21 | "Secondary": "secondary_text_color", 22 | "Hint": "disabled_hint_text_color", 23 | "Error": "error_color", 24 | "OP": { 25 | "primary": "opposite_text_color", 26 | "Secondary": "opposite_secondary_text_color", 27 | "Hint": "opposite_disabled_hint_text_color", 28 | }, 29 | } 30 | 31 | Builder.load_string( 32 | """ 33 | #:import md_icons kivymd.icon_definitions.md_icons 34 | 35 | 36 | 37 | disabled_color: self.theme_cls.disabled_hint_text_color 38 | text_size: self.width, None 39 | 40 | 41 | : 42 | font_style: "Icon" 43 | text: u"{}".format(md_icons[self.icon]) if self.icon in md_icons else "" 44 | source: None if self.icon in md_icons else self.icon 45 | canvas: 46 | Color: 47 | rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) 48 | Rectangle: 49 | source: self.source if self.source else None 50 | pos: self.pos 51 | size: self.size 52 | """ 53 | ) 54 | 55 | 56 | class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): 57 | font_style = StringProperty("Body1") 58 | """ 59 | Label font style. 60 | 61 | Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, `'H6'`, 62 | `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`, 63 | `'Caption'`, `'Overline'`, `'Icon'`. 64 | 65 | :attr:`font_style` is an :class:`~kivy.properties.StringProperty` 66 | and defaults to `'Body1'`. 67 | """ 68 | 69 | _capitalizing = BooleanProperty(False) 70 | 71 | def _get_text(self): 72 | if self._capitalizing: 73 | return self._text.upper() 74 | return self._text 75 | 76 | def _set_text(self, value): 77 | self._text = value 78 | 79 | _text = StringProperty() 80 | 81 | text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"]) 82 | """Text of the label.""" 83 | 84 | theme_text_color = OptionProperty( 85 | "Primary", 86 | allownone=True, 87 | options=[ 88 | "Primary", 89 | "Secondary", 90 | "Hint", 91 | "Error", 92 | "Custom", 93 | "ContrastParentBackground", 94 | ], 95 | ) 96 | """ 97 | Label color scheme name. 98 | 99 | Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`, 100 | `'Custom'`, `'ContrastParentBackground'`. 101 | 102 | :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` 103 | and defaults to `None`. 104 | """ 105 | 106 | text_color = ColorProperty(None) 107 | """Label text color in ``rgba`` format. 108 | 109 | :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` 110 | and defaults to `None`. 111 | """ 112 | _text_color_str = StringProperty() 113 | 114 | parent_background = ColorProperty(None) 115 | can_capitalize = BooleanProperty(True) 116 | 117 | def __init__(self, **kwargs): 118 | super().__init__(**kwargs) 119 | self.bind( 120 | font_style=self.update_font_style, 121 | can_capitalize=self.update_font_style, 122 | ) 123 | self.on_theme_text_color(None, self.theme_text_color) 124 | self.update_font_style() 125 | self.on_opposite_colors(None, self.opposite_colors) 126 | Clock.schedule_once(self.check_font_styles) 127 | self.theme_cls.bind(theme_style=self._do_update_theme_color) 128 | 129 | def check_font_styles(self, *dt): 130 | if self.font_style not in list(self.theme_cls.font_styles.keys()): 131 | raise ValueError( 132 | f"MDLabel.font_style is set to an invalid option '{self.font_style}'." 133 | f"Must be one of: {list(self.theme_cls.font_styles)}" 134 | ) 135 | else: 136 | return True 137 | 138 | def update_font_style(self, *args): 139 | if self.check_font_styles() is True: 140 | font_info = self.theme_cls.font_styles[self.font_style] 141 | self.font_name = font_info[0] 142 | self.font_size = sp(font_info[1]) 143 | if font_info[2] and self.can_capitalize: 144 | self._capitalizing = True 145 | else: 146 | self._capitalizing = False 147 | 148 | # TODO: Add letter spacing change 149 | # self.letter_spacing = font_info[3] 150 | 151 | def on_theme_text_color(self, instance, value): 152 | op = self.opposite_colors 153 | if op: 154 | self._text_color_str = __MDLabel_colors__.get("OP", "").get( 155 | value, "" 156 | ) 157 | else: 158 | self._text_color_str = __MDLabel_colors__.get(value, "") 159 | if self._text_color_str: 160 | self._do_update_theme_color() 161 | else: 162 | # 'Custom' and 'ContrastParentBackground' lead here, as well as the 163 | # generic None value it's not yet been set 164 | self._text_color_str = "" 165 | if value == "Custom" and self.text_color: 166 | self.color = self.text_color 167 | elif value == "ContrastParentBackground" and self.parent_background: 168 | self.color = get_contrast_text_color(self.parent_background) 169 | else: 170 | self.color = [0, 0, 0, 1] 171 | 172 | def _do_update_theme_color(self, *arguments): 173 | if self._text_color_str: 174 | self.color = getattr(self.theme_cls, self._text_color_str) 175 | 176 | def on_text_color(self, *args): 177 | if self.theme_text_color == "Custom": 178 | self.color = self.text_color 179 | 180 | def on_opposite_colors(self, instance, value): 181 | self.on_theme_text_color(self, self.theme_text_color) 182 | 183 | 184 | class MDIcon(MDLabel): 185 | icon = StringProperty("android") 186 | """ 187 | Label icon name. 188 | 189 | :attr:`icon` is an :class:`~kivy.properties.StringProperty` 190 | and defaults to `'android'`. 191 | """ 192 | 193 | source = StringProperty(None, allownone=True) 194 | """ 195 | Path to icon. 196 | 197 | :attr:`source` is an :class:`~kivy.properties.StringProperty` 198 | and defaults to `None`. 199 | """ 200 | -------------------------------------------------------------------------------- /kivymd/uix/relativelayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Relative Layout 3 | ========================== 4 | 5 | :class:`~kivy.uix.relativelayout.RelativeLayout` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | RelativeLayout 9 | -------------- 10 | 11 | .. code-block:: 12 | 13 | RelativeLayout: 14 | canvas: 15 | Color: 16 | rgba: app.theme_cls.primary_color 17 | RoundedRectangle: 18 | pos: (0, 0) 19 | size: self.size 20 | radius: [25, ] 21 | 22 | MDRelativeLayout 23 | ---------------- 24 | 25 | .. code-block:: 26 | 27 | MDRelativeLayout: 28 | radius: [25, ] 29 | md_bg_color: app.theme_cls.primary_color 30 | """ 31 | 32 | from kivy.uix.relativelayout import RelativeLayout 33 | 34 | from kivymd.uix import MDAdaptiveWidget 35 | 36 | 37 | class MDRelativeLayout(RelativeLayout, MDAdaptiveWidget): 38 | pass 39 | -------------------------------------------------------------------------------- /kivymd/uix/screen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/Screen 3 | ================= 4 | 5 | :class:`~kivy.uix.screenmanager.Screen` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | Screen 9 | ------ 10 | 11 | .. code-block:: 12 | 13 | Screen: 14 | canvas: 15 | Color: 16 | rgba: app.theme_cls.primary_color 17 | RoundedRectangle: 18 | pos: self.pos 19 | size: self.size 20 | radius: [25, 0, 0, 0] 21 | 22 | MDScreen 23 | -------- 24 | 25 | .. code-block:: 26 | 27 | MDScreen: 28 | radius: [25, 0, 0, 0] 29 | md_bg_color: app.theme_cls.primary_color 30 | """ 31 | 32 | from kivy.uix.screenmanager import Screen 33 | 34 | from kivymd.uix import MDAdaptiveWidget 35 | 36 | 37 | class MDScreen(Screen, MDAdaptiveWidget): 38 | pass 39 | -------------------------------------------------------------------------------- /kivymd/uix/stacklayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Components/StackLayout 3 | ====================== 4 | 5 | :class:`~kivy.uix.stacklayout.StackLayout` class equivalent. Simplifies working 6 | with some widget properties. For example: 7 | 8 | StackLayout 9 | ----------- 10 | 11 | .. code-block:: 12 | 13 | StackLayout: 14 | size_hint_y: None 15 | height: self.minimum_height 16 | 17 | canvas: 18 | Color: 19 | rgba: app.theme_cls.primary_color 20 | Rectangle: 21 | pos: self.pos 22 | size: self.size 23 | 24 | MDStackLayout 25 | ------------- 26 | 27 | .. code-block:: 28 | 29 | MDStackLayout: 30 | adaptive_height: True 31 | md_bg_color: app.theme_cls.primary_color 32 | 33 | Available options are: 34 | ---------------------- 35 | 36 | - adaptive_height_ 37 | - adaptive_width_ 38 | - adaptive_size_ 39 | 40 | .. adaptive_height: 41 | adaptive_height 42 | --------------- 43 | 44 | .. code-block:: kv 45 | 46 | adaptive_height: True 47 | 48 | Equivalent 49 | 50 | .. code-block:: kv 51 | 52 | size_hint_y: None 53 | height: self.minimum_height 54 | 55 | .. adaptive_width: 56 | adaptive_width 57 | -------------- 58 | 59 | .. code-block:: kv 60 | 61 | adaptive_width: True 62 | 63 | Equivalent 64 | 65 | .. code-block:: kv 66 | 67 | size_hint_x: None 68 | width: self.minimum_width 69 | 70 | .. adaptive_size: 71 | adaptive_size 72 | ------------- 73 | 74 | .. code-block:: kv 75 | 76 | adaptive_size: True 77 | 78 | Equivalent 79 | 80 | .. code-block:: kv 81 | 82 | size_hint: None, None 83 | size: self.minimum_size 84 | """ 85 | 86 | from kivy.uix.stacklayout import StackLayout 87 | 88 | from kivymd.uix import MDAdaptiveWidget 89 | 90 | 91 | class MDStackLayout(StackLayout, MDAdaptiveWidget): 92 | pass 93 | -------------------------------------------------------------------------------- /kivymd/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/utils/__init__.py -------------------------------------------------------------------------------- /kivymd/utils/asynckivy.py: -------------------------------------------------------------------------------- 1 | """ 2 | asynckivy 3 | ========= 4 | 5 | Copyright (c) 2019 Nattōsai Mitō 6 | 7 | GitHub - 8 | https://github.com/gottadiveintopython 9 | GitHub Gist - 10 | https://gist.github.com/gottadiveintopython/5f4a775849f9277081c396de65dc57c1 11 | 12 | """ 13 | 14 | __all__ = ("start", "sleep", "event") 15 | 16 | import types 17 | from collections import namedtuple 18 | from functools import partial 19 | 20 | from kivy.clock import Clock 21 | 22 | CallbackParameter = namedtuple("CallbackParameter", ("args", "kwargs")) 23 | 24 | 25 | def start(coro): 26 | def step(*args, **kwargs): 27 | try: 28 | coro.send(CallbackParameter(args, kwargs))(step) 29 | except StopIteration: 30 | pass 31 | 32 | try: 33 | coro.send(None)(step) 34 | except StopIteration: 35 | pass 36 | 37 | 38 | @types.coroutine 39 | def sleep(duration): 40 | # The partial() here looks meaningless. But this is needed in order 41 | # to avoid weak reference. 42 | param = yield lambda step_coro: Clock.schedule_once( 43 | partial(step_coro), duration 44 | ) 45 | return param.args[0] 46 | 47 | 48 | class event: 49 | def __init__(self, ed, name): 50 | self.bind_id = None 51 | self.ed = ed 52 | self.name = name 53 | 54 | def bind(self, step_coro): 55 | self.bind_id = bind_id = self.ed.fbind(self.name, self.callback) 56 | assert bind_id > 0 # check if binding succeeded 57 | self.step_coro = step_coro 58 | 59 | def callback(self, *args, **kwargs): 60 | self.parameter = CallbackParameter(args, kwargs) 61 | ed = self.ed 62 | ed.unbind_uid(self.name, self.bind_id) 63 | self.step_coro() 64 | 65 | def __await__(self): 66 | yield self.bind 67 | return self.parameter 68 | -------------------------------------------------------------------------------- /kivymd/utils/cropimage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Crop Image 3 | ========== 4 | """ 5 | 6 | 7 | def crop_image( 8 | cutting_size, 9 | path_to_image, 10 | path_to_save_crop_image, 11 | corner=0, 12 | blur=0, 13 | corner_mode="all", 14 | ): 15 | """Call functions of cropping/blurring/rounding image. 16 | 17 | cutting_size: size to which the image will be cropped; 18 | path_to_image: path to origin image; 19 | path_to_save_crop_image: path to new image; 20 | corner: value of rounding corners; 21 | blur: blur value; 22 | corner_mode: 'all'/'top'/'bottom' - indicates which corners to round out; 23 | 24 | """ 25 | 26 | im = _crop_image(cutting_size, path_to_image, path_to_save_crop_image) 27 | if corner: 28 | im = add_corners(im, corner, corner_mode) 29 | if blur: 30 | im = add_blur(im, blur) 31 | try: 32 | im.save(path_to_save_crop_image) 33 | except IOError: 34 | im.save(path_to_save_crop_image, "JPEG") 35 | 36 | 37 | def add_blur(im, mode): 38 | from PIL import ImageFilter 39 | 40 | im = im.filter(ImageFilter.GaussianBlur(mode)) 41 | 42 | return im 43 | 44 | 45 | def _crop_image(cutting_size, path_to_image, path_to_save_crop_image): 46 | from PIL import Image, ImageOps 47 | 48 | image = Image.open(path_to_image) 49 | image = ImageOps.fit(image, cutting_size) 50 | image.save(path_to_save_crop_image) 51 | 52 | return image 53 | 54 | 55 | def add_corners(im, corner, corner_mode): 56 | def add_top_corners(): 57 | alpha.paste(circle.crop((0, 0, corner, corner)), (0, 0)) 58 | alpha.paste( 59 | circle.crop((corner, 0, corner * 2, corner)), (w - corner, 0) 60 | ) 61 | 62 | def add_bottom_corners(): 63 | alpha.paste( 64 | circle.crop((0, corner, corner, corner * 2)), (0, h - corner) 65 | ) 66 | alpha.paste( 67 | circle.crop((corner, corner, corner * 2, corner * 2)), 68 | (w - corner, h - corner), 69 | ) 70 | 71 | from PIL import Image, ImageDraw 72 | 73 | circle = Image.new("L", (corner * 2, corner * 2), 0) 74 | draw = ImageDraw.Draw(circle) 75 | draw.ellipse((0, 0, corner * 2, corner * 2), fill=255) 76 | alpha = Image.new("L", im.size, 255) 77 | w, h = im.size 78 | 79 | if corner_mode == "all": 80 | add_top_corners() 81 | add_bottom_corners() 82 | elif corner_mode == "top": 83 | add_top_corners() 84 | if corner_mode == "bottom": 85 | add_bottom_corners() 86 | im.putalpha(alpha) 87 | 88 | return im 89 | 90 | 91 | def prepare_mask(size, antialias=2): 92 | from PIL import Image, ImageDraw 93 | 94 | mask = Image.new("L", (size[0] * antialias, size[1] * antialias), 0) 95 | ImageDraw.Draw(mask).ellipse((0, 0) + mask.size, fill=255) 96 | return mask.resize(size, Image.ANTIALIAS) 97 | 98 | 99 | def _crop_round_image(im, s): 100 | from PIL import Image 101 | 102 | w, h = im.size 103 | k = w // s[0] - h // s[1] 104 | if k > 0: 105 | im = im.crop(((w - h) // 2, 0, (w + h) // 2, h)) 106 | elif k < 0: 107 | im = im.crop((0, (h - w) // 2, w, (h + w) // 2)) 108 | return im.resize(s, Image.ANTIALIAS) 109 | 110 | 111 | def crop_round_image(cutting_size, path_to_image, path_to_new_image): 112 | from PIL import Image 113 | 114 | im = Image.open(path_to_image) 115 | im = _crop_round_image(im, cutting_size) 116 | im.putalpha(prepare_mask(cutting_size, 4)) 117 | im.save(path_to_new_image) 118 | -------------------------------------------------------------------------------- /kivymd/utils/fitimage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fit Image 3 | ========= 4 | 5 | Feature to automatically crop a `Kivy` image to fit your layout 6 | Write by Benedikt Zwölfer 7 | 8 | Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad 9 | 10 | 11 | Example: 12 | ======== 13 | 14 | .. code-block:: kv 15 | 16 | BoxLayout: 17 | size_hint_y: None 18 | height: "200dp" 19 | orientation: 'vertical' 20 | 21 | FitImage: 22 | size_hint_y: 3 23 | source: 'images/img1.jpg' 24 | 25 | FitImage: 26 | size_hint_y: 1 27 | source: 'images/img2.jpg' 28 | 29 | Example with round corners: 30 | =========================== 31 | 32 | .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fitimage-round-corners.png 33 | :align: center 34 | 35 | .. code-block:: python 36 | 37 | from kivy.uix.modalview import ModalView 38 | from kivy.lang import Builder 39 | 40 | from kivymd import images_path 41 | from kivymd.app import MDApp 42 | from kivymd.uix.card import MDCard 43 | 44 | Builder.load_string( 45 | ''' 46 | : 47 | elevation: 10 48 | radius: [36, ] 49 | 50 | FitImage: 51 | id: bg_image 52 | source: "images/bg.png" 53 | size_hint_y: .35 54 | pos_hint: {"top": 1} 55 | radius: [36, 36, 0, 0, ] 56 | ''') 57 | 58 | 59 | class Card(MDCard): 60 | pass 61 | 62 | 63 | class Example(MDApp): 64 | def build(self): 65 | modal = ModalView( 66 | size_hint=(0.4, 0.8), 67 | background=f"{images_path}/transparent.png", 68 | overlay_color=(0, 0, 0, 0), 69 | ) 70 | modal.add_widget(Card()) 71 | modal.open() 72 | 73 | 74 | Example().run() 75 | """ 76 | 77 | __all__ = ("FitImage",) 78 | 79 | from kivy.clock import Clock 80 | from kivy.graphics.context_instructions import Color 81 | from kivy.graphics.vertex_instructions import Rectangle 82 | from kivy.lang import Builder 83 | from kivy.properties import BooleanProperty, ListProperty, ObjectProperty 84 | from kivy.uix.boxlayout import BoxLayout 85 | from kivy.uix.image import AsyncImage 86 | from kivy.uix.widget import Widget 87 | 88 | Builder.load_string( 89 | """ 90 | 91 | canvas.before: 92 | StencilPush 93 | RoundedRectangle: 94 | size: self.size 95 | pos: self.pos 96 | radius: root.radius 97 | StencilUse 98 | 99 | canvas.after: 100 | StencilUnUse 101 | RoundedRectangle: 102 | size: self.size 103 | pos: self.pos 104 | radius: root.radius 105 | StencilPop 106 | """ 107 | ) 108 | 109 | 110 | class FitImage(BoxLayout): 111 | source = ObjectProperty() 112 | container = ObjectProperty() 113 | radius = ListProperty([0, 0, 0, 0]) 114 | mipmap = BooleanProperty(False) 115 | 116 | def __init__(self, **kwargs): 117 | super().__init__(**kwargs) 118 | Clock.schedule_once(self._late_init) 119 | 120 | def _late_init(self, *args): 121 | self.container = Container(self.source, self.mipmap) 122 | self.bind(source=self.container.setter("source")) 123 | self.add_widget(self.container) 124 | 125 | def reload(self): 126 | self.container.image.reload() 127 | 128 | 129 | class Container(Widget): 130 | source = ObjectProperty() 131 | image = ObjectProperty() 132 | 133 | def __init__(self, source, mipmap, **kwargs): 134 | super().__init__(**kwargs) 135 | self.image = AsyncImage(mipmap=mipmap) 136 | self.image.bind(on_load=self.adjust_size) 137 | self.source = source 138 | self.bind(size=self.adjust_size, pos=self.adjust_size) 139 | 140 | def on_source(self, instance, value): 141 | if isinstance(value, str): 142 | self.image.source = value 143 | else: 144 | self.image.texture = value 145 | self.adjust_size() 146 | 147 | def adjust_size(self, *args): 148 | if not self.parent or not self.image.texture: 149 | return 150 | 151 | (par_x, par_y) = self.parent.size 152 | 153 | if par_x == 0 or par_y == 0: 154 | with self.canvas: 155 | self.canvas.clear() 156 | return 157 | 158 | par_scale = par_x / par_y 159 | (img_x, img_y) = self.image.texture.size 160 | img_scale = img_x / img_y 161 | 162 | if par_scale > img_scale: 163 | (img_x_new, img_y_new) = (img_x, img_x / par_scale) 164 | else: 165 | (img_x_new, img_y_new) = (img_y * par_scale, img_y) 166 | 167 | crop_pos_x = (img_x - img_x_new) / 2 168 | crop_pos_y = (img_y - img_y_new) / 2 169 | 170 | subtexture = self.image.texture.get_region( 171 | crop_pos_x, crop_pos_y, img_x_new, img_y_new 172 | ) 173 | 174 | with self.canvas: 175 | self.canvas.clear() 176 | Color(1, 1, 1) 177 | Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y)) 178 | -------------------------------------------------------------------------------- /kivymd/utils/fpsmonitor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Monitor module 3 | ============== 4 | 5 | The Monitor module is a toolbar that shows the activity of your current 6 | application : 7 | 8 | * FPS 9 | 10 | """ 11 | 12 | from kivy.clock import Clock 13 | from kivy.lang import Builder 14 | from kivy.properties import NumericProperty, StringProperty 15 | from kivy.uix.label import Label 16 | 17 | Builder.load_string( 18 | """ 19 | : 20 | size_hint_y: None 21 | height: self.texture_size[1] 22 | text: root._fsp_value 23 | pos_hint: {"top": 1} 24 | 25 | canvas.before: 26 | Color: 27 | rgba: [0,0,0,0] 28 | Rectangle: 29 | pos: self.pos 30 | size: self.size 31 | """ 32 | ) 33 | 34 | 35 | class FpsMonitor(Label): 36 | updated_interval = NumericProperty(0.5) 37 | """FPS refresh rate.""" 38 | 39 | _fsp_value = StringProperty() 40 | 41 | def start(self): 42 | Clock.schedule_interval(self.update_fps, self.updated_interval) 43 | 44 | def update_fps(self, *args): 45 | self._fsp_value = "FPS: %f" % Clock.get_fps() 46 | -------------------------------------------------------------------------------- /kivymd/utils/hot_reload_viewer.py: -------------------------------------------------------------------------------- 1 | """ 2 | HotReloadViewer 3 | =============== 4 | 5 | .. Note:: The :class:`~HotReloadViewer` class is based on 6 | the `KvViewerApp `_ class 7 | 8 | :class:`~HotReloadViewer`, for KV-Viewer, is a simple tool allowing you to 9 | dynamically display a KV file, taking its changes into account 10 | (thanks to watchdog). The idea is to facilitate design using the KV language. 11 | 12 | Usage 13 | ----- 14 | 15 | .. code-block:: python 16 | 17 | from kivy.lang import Builder 18 | 19 | from kivymd.app import MDApp 20 | 21 | KV = ''' 22 | #:import KivyLexer kivy.extras.highlight.KivyLexer 23 | #:import HotReloadViewer kivymd.utils.hot_reload_viewer.HotReloadViewer 24 | 25 | 26 | BoxLayout: 27 | 28 | CodeInput: 29 | lexer: KivyLexer() 30 | style_name: "native" 31 | on_text: app.update_kv_file(self.text) 32 | size_hint_x: .7 33 | 34 | HotReloadViewer: 35 | size_hint_x: .3 36 | path: app.path_to_kv_file 37 | errors: True 38 | errors_text_color: 1, 1, 0, 1 39 | errors_background_color: app.theme_cls.bg_dark 40 | ''' 41 | 42 | 43 | class Example(MDApp): 44 | path_to_kv_file = "kv_file.kv" 45 | 46 | def build(self): 47 | self.theme_cls.theme_style = "Dark" 48 | return Builder.load_string(KV) 49 | 50 | def update_kv_file(self, text): 51 | with open(self.path_to_kv_file, "w") as kv_file: 52 | kv_file.write(text) 53 | 54 | 55 | Example().run() 56 | 57 | This will display the test.kv and automatically update the display when the 58 | file changes. 59 | 60 | .. raw:: html 61 | 62 |
63 | 64 |
65 | 66 | 67 | .. rubric:: This scripts uses watchdog to listen for file changes. To install 68 | watchdog. 69 | 70 | .. code-block:: bash 71 | 72 | pip install watchdog 73 | """ 74 | 75 | import os 76 | 77 | from kivy.clock import Clock, mainthread 78 | from kivy.lang import Builder 79 | from kivy.properties import BooleanProperty, ColorProperty, StringProperty 80 | from kivy.uix.scrollview import ScrollView 81 | from watchdog.events import FileSystemEventHandler 82 | from watchdog.observers import Observer 83 | 84 | from kivymd.theming import ThemableBehavior 85 | from kivymd.uix.boxlayout import MDBoxLayout 86 | 87 | Builder.load_string( 88 | """ 89 | 90 | 91 | MDLabel: 92 | size_hint_y: None 93 | height: self.texture_size[1] 94 | theme_text_color: "Custom" 95 | text_color: 96 | root.errors_text_color if root.errors_text_color \ 97 | else root.theme_cls.text_color 98 | text: root.text 99 | """ 100 | ) 101 | 102 | 103 | class HotReloadErrorText(ThemableBehavior, ScrollView): 104 | text = StringProperty() 105 | """Text errors. 106 | 107 | :attr:`text` is an :class:`~kivy.properties.StringProperty` 108 | and defaults to `''`. 109 | """ 110 | 111 | errors_text_color = ColorProperty(None) 112 | """ 113 | Error text color. 114 | 115 | :attr:`errors_text_color` is an :class:`~kivy.properties.ColorProperty` 116 | and defaults to `None`. 117 | """ 118 | 119 | 120 | class HotReloadHandler(FileSystemEventHandler): 121 | def __init__(self, callback, target, **kwargs): 122 | super().__init__(**kwargs) 123 | self.callback = callback 124 | self.target = target 125 | 126 | def on_any_event(self, event): 127 | self.callback() 128 | 129 | 130 | class HotReloadViewer(ThemableBehavior, MDBoxLayout): 131 | """ 132 | :Events: 133 | :attr:`on_error` 134 | Called when an error occurs in the KV-file that the user is editing. 135 | """ 136 | 137 | path = StringProperty() 138 | """Path to KV file. 139 | 140 | :attr:`path` is an :class:`~kivy.properties.StringProperty` 141 | and defaults to `''`. 142 | """ 143 | 144 | errors = BooleanProperty(False) 145 | """ 146 | Show errors while editing KV-file. 147 | 148 | :attr:`errors` is an :class:`~kivy.properties.BooleanProperty` 149 | and defaults to `False`. 150 | """ 151 | 152 | errors_background_color = ColorProperty(None) 153 | """ 154 | Error background color. 155 | 156 | :attr:`errors_background_color` is an :class:`~kivy.properties.ColorProperty` 157 | and defaults to `None`. 158 | """ 159 | 160 | errors_text_color = ColorProperty(None) 161 | """ 162 | Error text color. 163 | 164 | :attr:`errors_text_color` is an :class:`~kivy.properties.ColorProperty` 165 | and defaults to `None`. 166 | """ 167 | 168 | _temp_widget = None 169 | 170 | def __init__(self, **kwargs): 171 | self.observer = Observer() 172 | self.error_text = HotReloadErrorText() 173 | super().__init__(**kwargs) 174 | self.register_event_type("on_error") 175 | 176 | @mainthread 177 | def update(self, *args): 178 | """Updates and displays the KV-file that the user edits.""" 179 | 180 | Builder.unload_file(self.path) 181 | self.clear_widgets() 182 | try: 183 | self.padding = (0, 0, 0, 0) 184 | self.md_bg_color = (0, 0, 0, 0) 185 | self._temp_widget = Builder.load_file(self.path) 186 | self.add_widget(self._temp_widget) 187 | except Exception as error: 188 | self.show_error(error) 189 | self.dispatch("on_error", error) 190 | 191 | def show_error(self, error): 192 | """Displays text with a current error.""" 193 | 194 | if self._temp_widget and not self.errors: 195 | self.add_widget(self._temp_widget) 196 | return 197 | else: 198 | if self.errors_background_color: 199 | self.md_bg_color = self.errors_background_color 200 | self.padding = ("4dp", "4dp", "4dp", "4dp") 201 | self.error_text.text = ( 202 | error.message 203 | if getattr(error, r"message", None) 204 | else str(error) 205 | ) 206 | self.add_widget(self.error_text) 207 | 208 | def on_error(self, *args): 209 | """ 210 | Called when an error occurs in the KV-file that the user is editing. 211 | """ 212 | 213 | def on_errors_text_color(self, instance, value): 214 | self.error_text.errors_text_color = value 215 | 216 | def on_path(self, instance, value): 217 | value = os.path.abspath(value) 218 | self.observer.schedule( 219 | HotReloadHandler(self.update, value), os.path.dirname(value) 220 | ) 221 | self.observer.start() 222 | Clock.schedule_once(self.update, 1) 223 | -------------------------------------------------------------------------------- /kivymd/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/kivymd/vendor/__init__.py -------------------------------------------------------------------------------- /kivymd/vendor/circleLayout/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Davide Depau 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 | 23 | -------------------------------------------------------------------------------- /kivymd/vendor/circleLayout/README.md: -------------------------------------------------------------------------------- 1 | CircularLayout 2 | ============== 3 | 4 | CircularLayout is a special layout that places widgets around a circle. 5 | 6 | See the widget's documentation and the example for more information. 7 | 8 | ![Screenshot](screenshot.png) 9 | 10 | size_hint 11 | --------- 12 | 13 | size_hint_x is used as an angle-quota hint (widget with higher 14 | size_hint_x will be farther from each other, and viceversa), while 15 | size_hint_y is used as a widget size hint (widgets with a higher size 16 | hint will be bigger).size_hint_x cannot be None. 17 | 18 | Widgets are all squares, unless you set size_hint_y to None (in that 19 | case you'll be able to specify your own size), and their size is the 20 | difference between the outer and the inner circle's radii. To make the 21 | widgets bigger you can just decrease inner_radius_hint. -------------------------------------------------------------------------------- /kivymd/vendor/circularTimePicker/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Davide Depau 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 | 23 | -------------------------------------------------------------------------------- /kivymd/vendor/circularTimePicker/README.md: -------------------------------------------------------------------------------- 1 | Circular Date & Time Picker for Kivy 2 | ==================================== 3 | 4 | (currently only time, date coming soon) 5 | 6 | Based on [CircularLayout](https://github.com/kivy-garden/garden.circularlayout). 7 | The main aim is to provide a date and time selector similar to the 8 | one found in Android KitKat+. 9 | 10 | ![Screenshot](screenshot.png) 11 | 12 | Simple usage 13 | ------------ 14 | 15 | Import the widget with 16 | 17 | ```python 18 | from kivy.garden.circulardatetimepicker import CircularTimePicker 19 | ``` 20 | 21 | then use it! That's it! 22 | 23 | ```python 24 | c = CircularTimePicker() 25 | c.bind(time=self.set_time) 26 | root.add_widget(c) 27 | ``` 28 | 29 | in Kv language: 30 | 31 | ``` 32 | : 33 | BoxLayout: 34 | orientation: "vertical" 35 | 36 | CircularTimePicker 37 | 38 | Button: 39 | text: "Dismiss" 40 | size_hint_y: None 41 | height: "40dp" 42 | on_release: root.dismiss() 43 | ``` -------------------------------------------------------------------------------- /libs/firebase.py: -------------------------------------------------------------------------------- 1 | from kivy.network.urlrequest import UrlRequest 2 | from kivy.logger import Logger 3 | from libs.utils import * 4 | import json 5 | 6 | # Store web api key in github actions secrets, create api_key.txt file using actions 7 | # then use WEB_API_KEY to store the key. 8 | WEB_API_KEY = os.environ["WEB_API_KEY"] 9 | DATABASE_URL = os.environ["DATABASE_URL"] 10 | 11 | 12 | class Firebase: 13 | BASE_URL = "https://www.googleapis.com/identitytoolkit/v3/relyingparty" 14 | SIGNUP_URL = f"{BASE_URL}/signupNewUser?key=" 15 | LOGIN_URL = f"{BASE_URL}/verifyPassword?key=" 16 | DELETE_URL = f"{BASE_URL}/deleteAccount?key=" 17 | 18 | def signup_success(self, req, result): 19 | """ 20 | Implement this method to handle the result of the successful signup request. 21 | """ 22 | Logger.warn("Firebase: Signup success not implemented") 23 | 24 | def signup_failure(self, req, result): 25 | """ 26 | Implement this method to handle the result of the signup failure request. 27 | """ 28 | Logger.warn("Firebase: Signup failure not implemented") 29 | 30 | def signup(self, name, password): 31 | """ 32 | Used to signup the user. 33 | """ 34 | url = self.SIGNUP_URL + WEB_API_KEY 35 | data = json.dumps( 36 | {"email": name, "password": password, "returnSecureToken": True} 37 | ) 38 | UrlRequest( 39 | url, 40 | req_body=data, 41 | on_success=self.signup_success, 42 | on_failure=self.signup_failure, 43 | timeout=15, 44 | ) 45 | 46 | def login_success(self, req, result): 47 | """ 48 | Implement this method to handle the result of the successful login request. 49 | """ 50 | Logger.warn("Firebase: Login success not implemented") 51 | 52 | def login_failure(self, req, result): 53 | """ 54 | Implement this method to handle the result of the login failure request. 55 | """ 56 | Logger.warn("Firebase: Login failure not implemented") 57 | 58 | def login(self, name, password): 59 | """ 60 | Used to login the user. 61 | """ 62 | 63 | url = self.LOGIN_URL + WEB_API_KEY 64 | data = json.dumps( 65 | {"email": name, "password": password, "returnSecureToken": True} 66 | ) 67 | UrlRequest( 68 | url, 69 | req_body=data, 70 | on_success=self.login_success, 71 | on_failure=self.login_failure, 72 | timeout=10, 73 | ) 74 | 75 | def backup_success(self, req, result): 76 | """ 77 | Override this method to handle the result of the successful backup request. 78 | """ 79 | Logger.warn(f"Firebase: Backup success not implemented, {result}") 80 | 81 | def backup_failure(self, req, result): 82 | """ 83 | Override this method to handle the result of the failure in backup request. 84 | """ 85 | Logger.warn(f"Firebase: Backup failure not implemented, {result}") 86 | 87 | def backup(self): 88 | """ 89 | Used to backup the user's passwords. 90 | """ 91 | result = load_passwords() 92 | _json = {} 93 | _json[get_uid()] = result 94 | UrlRequest( 95 | DATABASE_URL + "/.json", 96 | req_body=json.dumps(_json), 97 | req_headers={"Content-type": "application/json", "Accept": "text/plain"}, 98 | on_success=self.backup_success, 99 | on_failure=self.backup_failure, 100 | on_error=self.backup_failure, 101 | method="PATCH", 102 | ) 103 | 104 | def restore_success(self, req, result): 105 | """ 106 | Override this method to handle the result of the successful restore request. 107 | """ 108 | Logger.warn(f"Firebase: Restore success not implemented, {result}") 109 | 110 | def restore_failure(self, req, result): 111 | """ 112 | Override this method to handle the result of the failure in restore request. 113 | """ 114 | Logger.warn(f"Firebase: Restore failure not implemented, {result}") 115 | 116 | def restore(self, user_id=None): 117 | """ 118 | Used to restore the user's passwords. 119 | """ 120 | if user_id is None: 121 | user_id = get_uid() 122 | 123 | UrlRequest( 124 | f"{DATABASE_URL}/{user_id}.json", 125 | on_success=self.restore_success, 126 | on_failure=self.restore_failure, 127 | on_error=self.restore_failure, 128 | ) 129 | -------------------------------------------------------------------------------- /libs/modules/AndroidAPI.py: -------------------------------------------------------------------------------- 1 | from kivy.utils import platform 2 | 3 | if platform == "android": 4 | from android.runnable import run_on_ui_thread 5 | from jnius import autoclass 6 | from jnius import JavaException 7 | 8 | Color = autoclass("android.graphics.Color") 9 | WindowManager = autoclass("android.view.WindowManager$LayoutParams") 10 | activity = autoclass("org.kivy.android.PythonActivity").mActivity 11 | View = autoclass("android.view.View") 12 | Configuration = autoclass("android.content.res.Configuration") 13 | 14 | def _class_call(cls, args: tuple, instantiate: bool): 15 | if not args: 16 | return cls() if instantiate else cls 17 | else: 18 | return cls(*args) 19 | 20 | def Rect(*args, instantiate: bool = False): 21 | return _class_call(autoclass("android.graphics.Rect"), args, instantiate) 22 | 23 | @run_on_ui_thread 24 | def fix_back_button(): 25 | activity.onWindowFocusChanged(False) 26 | activity.onWindowFocusChanged(True) 27 | 28 | def keyboard_height(): 29 | try: 30 | decor_view = activity.getWindow().getDecorView() 31 | height = activity.getWindowManager().getDefaultDisplay().getHeight() 32 | decor_view.getWindowVisibleDisplayFrame(Rect) 33 | return height - Rect().bottom 34 | 35 | except JavaException: 36 | return 0 37 | 38 | @run_on_ui_thread 39 | def statusbar( 40 | theme="Custom", 41 | status_color="121212", 42 | nav_color=None, 43 | white_text=None, 44 | full=False, 45 | ): 46 | window = activity.getWindow() 47 | print("Setting StatusBar Color") # Updates everytime color is changed 48 | window.clearFlags(WindowManager.FLAG_TRANSLUCENT_STATUS) 49 | try: 50 | window.addFlags(WindowManager.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) 51 | if theme == "black": 52 | window.setNavigationBarColor(Color.parseColor("#" + status_color)) 53 | window.setStatusBarColor(Color.parseColor("#" + status_color)) 54 | window.getDecorView().setSystemUiVisibility(0) 55 | elif theme == "white": 56 | window.setNavigationBarColor(Color.parseColor("#FAFAFA")) 57 | window.setStatusBarColor(Color.parseColor("#FAFAFA")) 58 | window.getDecorView().setSystemUiVisibility( 59 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 60 | | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 61 | ) 62 | elif theme == "Custom": 63 | if nav_color is None: 64 | nav_color = status_color 65 | window.setNavigationBarColor(Color.parseColor("#" + nav_color)) 66 | window.setStatusBarColor(Color.parseColor("#" + status_color)) 67 | if white_text is True: 68 | window.getDecorView().setSystemUiVisibility( 69 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 70 | | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 71 | ) 72 | elif white_text is False: 73 | window.getDecorView().setSystemUiVisibility(0) 74 | 75 | except Exception: 76 | pass 77 | 78 | def android_dark_mode(): 79 | night_mode_flags = ( 80 | activity.getContext().getResources().getConfiguration().uiMode 81 | & Configuration.UI_MODE_NIGHT_MASK 82 | ) 83 | if night_mode_flags == Configuration.UI_MODE_NIGHT_YES: 84 | return True 85 | elif night_mode_flags in [ 86 | Configuration.UI_MODE_NIGHT_NO, 87 | Configuration.UI_MODE_NIGHT_UNDEFINED, 88 | ]: 89 | return False 90 | 91 | def orientation(): 92 | config = activity.getResources().getConfiguration() 93 | if config.orientation == 1: 94 | return "Portrait" 95 | else: 96 | return "Landscape" 97 | -------------------------------------------------------------------------------- /libs/modules/List.py: -------------------------------------------------------------------------------- 1 | from kivy.animation import Animation 2 | from kivy.lang import Builder 3 | from kivy.properties import BooleanProperty, StringProperty, ColorProperty, DictProperty 4 | from kivy.uix.behaviors import ButtonBehavior 5 | from kivy.uix.recycleview.views import RecycleDataViewBehavior 6 | 7 | from kivymd.app import MDApp 8 | from kivymd.material_resources import dp 9 | from kivymd.theming import ThemableBehavior 10 | from kivymd.uix.behaviors import RectangularRippleBehavior 11 | from kivymd.uix.boxlayout import MDBoxLayout 12 | 13 | app = MDApp.get_running_app() 14 | 15 | KV = """ 16 | 17 | orientation:'vertical' 18 | size_hint_y:None 19 | padding: dp(28),dp(0),dp(10),dp(15) 20 | size_hint_x:1 21 | spacing:'6dp' 22 | height:'90dp' if self.selected else "56dp" 23 | elevation:0 24 | radius:'28dp' 25 | disable: True 26 | md_bg_color:self.list_color_active if self.selected else app.theme_cls.primary_light[:-1] + [0] 27 | MDLabel: 28 | adaptive_height: True 29 | text:root.name 30 | theme_text_color:"Custom" 31 | text_color: app.text_color 32 | MDBoxLayout: 33 | id: sec_box 34 | size_hint_y:None 35 | height:root.selected*dp(30) 36 | opacity: root.selected*1 37 | MDLabel: 38 | adaptive_height:True 39 | text:root.password 40 | theme_text_color:"Secondary" 41 | MDIconButton: 42 | id: copy_button 43 | disabled: not root.selected 44 | md_bg_color_disabled:[0,0,0,0] 45 | theme_text_color:"Custom" 46 | text_color: app.text_color 47 | icon:'content-copy' 48 | pos_hint:{'center_y':.5} 49 | MDIconButton: 50 | id: update_button 51 | disabled: not root.selected 52 | md_bg_color_disabled:[0,0,0,0] 53 | icon:'pencil-outline' 54 | theme_text_color:"Custom" 55 | text_color: app.text_color 56 | pos_hint:{'center_y':.5} 57 | # text_color:[.3,.3,.3,1] 58 | MDIconButton: 59 | id: delete_button 60 | disabled: not root.selected 61 | md_bg_color_disabled:[0,0,0,0] 62 | icon:'trash-can-outline' 63 | theme_text_color:"Custom" 64 | text_color: app.text_color 65 | pos_hint:{'center_y':.5} 66 | 67 | """ 68 | 69 | 70 | class List( 71 | RectangularRippleBehavior, 72 | RecycleDataViewBehavior, 73 | ThemableBehavior, 74 | ButtonBehavior, 75 | MDBoxLayout, 76 | ): 77 | Builder.load_string(KV) 78 | 79 | name = StringProperty("Google") 80 | password = StringProperty("12345678910111213") 81 | selected = BooleanProperty() 82 | list_color_active = ColorProperty() 83 | button_actions = DictProperty() 84 | 85 | _no_ripple_effect = True 86 | ripple_alpha = 0.1 87 | 88 | def on_button_actions(self, *args): 89 | if len(self.button_actions) == 3: 90 | self.ids.copy_button.on_release = self.button_actions["copy"] 91 | self.ids.update_button.on_release = self.button_actions["update"] 92 | self.ids.delete_button.on_release = self.button_actions["delete"] 93 | 94 | def on_release(self): 95 | if self.selected: 96 | if self.right - self.last_touch.x >= dp(170): 97 | self.parent.clear_selection() 98 | else: 99 | recycle_list = self.parent.children 100 | snack = app.root.HomeScreen.ids.find.snackbar 101 | 102 | if ( 103 | not (snack and snack.is_open) 104 | or self not in recycle_list[:2] 105 | or len(recycle_list) <= 13 106 | ): 107 | self.parent.select_with_touch(self.index, None) 108 | 109 | def refresh_view_attrs(self, rv, index, data): 110 | self.index = index 111 | return super().refresh_view_attrs(rv, index, data) 112 | 113 | def apply_selection(self, rv, index, is_selected): 114 | try: 115 | self.selected = is_selected 116 | rv.data[index]["selected"] = is_selected 117 | except IndexError: 118 | print(index, self.selected, "Index error") 119 | 120 | def on_selected(self, instance, selected): 121 | if selected: 122 | self.list_color_active = self.theme_cls.primary_light[:-1] + [0] 123 | Animation( 124 | list_color_active=self.theme_cls.primary_light[:-1] + [0.3], d=0.1 125 | ).start(self) 126 | -------------------------------------------------------------------------------- /libs/modules/Toolbar.py: -------------------------------------------------------------------------------- 1 | from kivy.lang import Builder 2 | from kivy.properties import StringProperty, NumericProperty, ListProperty, ColorProperty 3 | 4 | from kivymd.app import MDApp 5 | from kivymd.theming import ThemableBehavior 6 | from kivymd.uix import SpecificBackgroundColorBehavior 7 | from kivymd.uix.behaviors import FakeRectangularElevationBehavior 8 | from kivymd.uix.boxlayout import MDBoxLayout 9 | from kivymd.uix.button import MDIconButton 10 | 11 | Builder.load_string(""" 12 | 13 | id: toolbox 14 | set_opacity: root.set_opacity 15 | size_hint_y: None 16 | elevation: root.elevation 17 | md_bg_color: root.md_bg_color if root.md_bg_color !=[0,0,0,0] else [0,0,0,0] 18 | pos_hint:{'top':1} 19 | spacing: '10dp' 20 | font_style:None 21 | height: root.theme_cls.standard_increment + root.increase_height 22 | 23 | MDBoxLayout: 24 | id: left_action_box 25 | adaptive_size: True 26 | pos_hint: {'center_y': .5} 27 | 28 | MDBoxLayout: 29 | adaptive_height: True 30 | pos_hint: {'center_y': .5} 31 | MDLabel: 32 | id:setting_label 33 | text: root.title 34 | theme_text_color:'Custom' 35 | # font_size: root.text_height if root.text_height else 0 36 | font_style: "H6" if not root.font_style else root.font_style 37 | font_name:'Poppins' 38 | text_color:app.theme_cls.text_color if root.icon_color is None else root.icon_color 39 | shorten: True 40 | shorten_from: "right" 41 | height: root.height-root.padding[0]/2 42 | 43 | MDBoxLayout: 44 | id: right_action_box 45 | adaptive_size: True 46 | spacing:'5dp' 47 | pos_hint: {'center_y': .5} 48 | 49 | 50 | 51 | 52 | """ 53 | ) 54 | 55 | 56 | class Toolbar( 57 | MDBoxLayout, 58 | ThemableBehavior, 59 | FakeRectangularElevationBehavior, 60 | SpecificBackgroundColorBehavior, 61 | ): 62 | elevation = NumericProperty(0) 63 | adaptive_height = True 64 | md_bg_color = ColorProperty() 65 | title = StringProperty("") 66 | increase_height = NumericProperty(0) 67 | left_action_items = ListProperty() 68 | right_action_items = ListProperty() 69 | icon_color = ColorProperty(None) 70 | text_height = NumericProperty(0) 71 | 72 | def __init__(self, **kwargs): 73 | super().__init__(**kwargs) 74 | 75 | def on_left_action_items(self, instance, value): 76 | self.ids.left_action_box.clear_widgets() 77 | for icon_left_action in self.left_action_items: 78 | if len(icon_left_action) == 1: 79 | self.icon_left = MDIconButton( 80 | icon=icon_left_action[0], 81 | theme_text_color="Custom", 82 | text_color=self.icon_color, 83 | user_font_size=self.text_height, 84 | pos_hint={"center_y": 0.5}, 85 | ) 86 | 87 | else: 88 | self.icon_left = MDIconButton( 89 | icon=icon_left_action[0], 90 | theme_text_color="Custom", 91 | text_color=self.icon_color, 92 | user_font_size=self.text_height, 93 | pos_hint={"center_y": 0.5}, 94 | on_release=icon_left_action[1], 95 | ) 96 | 97 | self.ids.left_action_box.add_widget(self.icon_left) 98 | 99 | def on_right_action_items(self, instance, value): 100 | self.ids.right_action_box.clear_widgets() 101 | for icon_right_action in self.right_action_items: 102 | if len(icon_right_action) == 1: 103 | self.icon_right = MDIconButton( 104 | icon=icon_right_action[0], 105 | theme_text_color="Custom", 106 | text_color=self.icon_color, 107 | user_font_size=self.text_height, 108 | pos_hint={"center_y": 0.5}, 109 | ) 110 | 111 | else: 112 | self.icon_right = MDIconButton( 113 | icon=icon_right_action[0], 114 | theme_text_color="Custom", 115 | text_color=self.icon_color, 116 | user_font_size=self.text_height, 117 | pos_hint={"center_y": 0.5}, 118 | on_release=icon_right_action[1], 119 | ) 120 | self.ids.right_action_box.add_widget(self.icon_right) 121 | 122 | def on_icon_color(self, instance, color): 123 | for icon in self.ids.left_action_box.children: 124 | icon.text_color = color 125 | for icon in self.ids.right_action_box.children: 126 | icon.text_color = color 127 | 128 | 129 | if __name__ == "__main__": 130 | 131 | class ToolbarApp(MDApp): 132 | def build(self): 133 | return Builder.load_string( 134 | """ 135 | Screen: 136 | Toolbar: 137 | title:'Test' 138 | icon_color: [1,0,.4,1] 139 | right_action_items:[['cog',lambda x: print('Call any function you want like this.')]] 140 | """ 141 | ) 142 | 143 | ToolbarApp().run() 144 | -------------------------------------------------------------------------------- /libs/modules/dialogs.py: -------------------------------------------------------------------------------- 1 | from kivy.animation import Animation 2 | from kivy.clock import Clock 3 | from kivy.core.window import Window 4 | from kivy.lang.builder import Builder 5 | from kivy.properties import ( 6 | ListProperty, 7 | NumericProperty, 8 | ObjectProperty, 9 | OptionProperty, 10 | StringProperty, 11 | ) 12 | from kivy.uix.boxlayout import BoxLayout 13 | from kivymd.uix.behaviors import FakeRectangularElevationBehavior 14 | from kivymd.uix.dialog import BaseDialog 15 | from kivymd.material_resources import dp 16 | 17 | Builder.load_string(""" 18 | #: import md_icons kivymd.icon_definitions.md_icons 19 | #: import Window kivy.core.window.Window 20 | 21 | : 22 | background_color: [0,0,0,0] 23 | overlay_color: [0,0,0,0.25] 24 | size_hint: alert.width / Window.width, alert.height / Window.height 25 | MainAlertBox: 26 | id: alert 27 | size_hint: None,None 28 | elevation: root.elevation 29 | size: root.size_portrait if root._orientation == "portrait" \ 30 | else root.size_landscape 31 | orientation: "vertical" if root._orientation == "portrait" \ 32 | else "horizontal" 33 | 34 | canvas.before: 35 | Color: 36 | rgba: root.bg_color if root.bg_color else root.theme_cls.bg_light 37 | RoundedRectangle: 38 | pos: self.pos 39 | size: self.size 40 | radius: [root.dialog_radius, ] 41 | 42 | canvas.after: 43 | Color: 44 | rgba: root.progress_color if root.progress_color else root.theme_cls.primary_dark 45 | RoundedRectangle: 46 | pos: self.pos[0] + root.dialog_radius, self.pos[1] + root.height - root.progress_width 47 | size: root._progress_value, root.progress_width 48 | radius: [root.progress_width / 2, ] 49 | 50 | BoxLayout: 51 | size_hint_y: None if root._orientation == "portrait" \ 52 | else 1 53 | 54 | size_hint_x: None if root._orientation == "landscape" \ 55 | else 1 56 | 57 | size: (root.width, root.header_height_portrait) if root._orientation == "portrait" \ 58 | else (root.header_width_landscape, root.height) 59 | 60 | canvas.before: 61 | Color: 62 | rgba: root.header_bg if root.header_bg else root.theme_cls.primary_color 63 | RoundedRectangle: 64 | pos: self.pos 65 | size: self.size 66 | radius: [root.dialog_radius, root.dialog_radius, 0, 0] if root._orientation == "portrait" \ 67 | else [root.dialog_radius, 0, 0, root.dialog_radius] 68 | 69 | MDLabel: 70 | font_style: "Icon" if root.header_text_type == "icon" else "Body1" 71 | bold: True 72 | text: u"{}".format(md_icons[root.header_icon]) if root.header_text_type == "icon" else root.header_text 73 | theme_text_color: "Custom" 74 | text_color: root.header_color if root.header_color else [1, 1, 1, 1] 75 | valign: root.header_v_pos 76 | halign: root.header_h_pos 77 | font_size: root.header_font_size 78 | 79 | BoxLayout: 80 | id: content 81 | """ 82 | ) 83 | 84 | 85 | class MainAlertBox(FakeRectangularElevationBehavior, BoxLayout): 86 | pass 87 | 88 | 89 | class AKAlertDialog(BaseDialog): 90 | 91 | dialog_radius = NumericProperty(dp(30)) 92 | radius = [dp(30)] * 4 93 | bg_color = ListProperty() 94 | auto_dismiss = True 95 | size_portrait = ListProperty(["250dp", "350dp"]) 96 | size_landscape = ListProperty(["400dp", "250dp"]) 97 | header_width_landscape = NumericProperty("110dp") 98 | header_height_portrait = NumericProperty("110dp") 99 | fixed_orientation = OptionProperty(None, options=["portrait", "landscape"]) 100 | header_bg = ListProperty() 101 | header_text_type = OptionProperty("icon", options=["icon", "text"]) 102 | header_text = StringProperty() 103 | header_icon = StringProperty("android") 104 | header_color = ListProperty() 105 | header_h_pos = StringProperty("center") 106 | header_v_pos = StringProperty("center") 107 | header_font_size = NumericProperty("55dp") 108 | progress_interval = NumericProperty(None) 109 | progress_width = NumericProperty("2dp") 110 | progress_color = ListProperty() 111 | elevation = NumericProperty(5) 112 | content_cls = ObjectProperty() 113 | _anim_duration = 0.25 114 | _orientation = StringProperty() 115 | _progress_value = NumericProperty() 116 | 117 | def __init__(self, **kwargs): 118 | super().__init__(**kwargs) 119 | Window.bind(on_resize=self._get_orientation) 120 | self.register_event_type("on_progress_finish") 121 | Clock.schedule_once(self._update) 122 | 123 | def _update(self, *args): 124 | self._get_orientation() 125 | 126 | def _get_orientation(self, *args): 127 | if self.fixed_orientation: 128 | self._orientation = self.fixed_orientation 129 | elif self.theme_cls.device_orientation == "portrait": 130 | self._orientation = "portrait" 131 | else: 132 | self._orientation = "landscape" 133 | 134 | def on_content_cls(self, *args): 135 | if not self.content_cls: 136 | return 137 | 138 | self.ids.content.clear_widgets() 139 | self.ids.content.add_widget(self.content_cls) 140 | 141 | def on_open(self): 142 | self._start_progress() 143 | return super().on_open() 144 | 145 | def on_pre_open(self): 146 | self._opening_animation() 147 | return super().on_pre_open() 148 | 149 | def on_dismiss(self): 150 | self._dismiss_animation() 151 | return super().on_dismiss() 152 | 153 | def _opening_animation(self): 154 | self.opacity = 0 155 | anim = Animation(opacity=1, duration=self._anim_duration, t="out_quad") 156 | anim.start(self) 157 | 158 | def _dismiss_animation(self): 159 | anim = Animation(opacity=0, duration=self._anim_duration - .05, t="out_quad") 160 | anim.start(self) 161 | 162 | def _start_progress(self): 163 | if not self.progress_interval: 164 | return 165 | max_width = self.size[0] - self.dialog_radius * 2 166 | anim = Animation(_progress_value=max_width, duration=self.progress_interval) 167 | anim.bind(on_complete=lambda x, y: self.dispatch("on_progress_finish")) 168 | anim.start(self) 169 | 170 | def on_progress_finish(self, *args): 171 | pass 172 | -------------------------------------------------------------------------------- /libs/modules/picker.py: -------------------------------------------------------------------------------- 1 | from kivy.factory import Factory 2 | from kivy.lang import Builder 3 | from kivy.properties import OptionProperty, get_color_from_hex 4 | from kivymd.app import MDApp 5 | from libs.modules.dialogs import AKAlertDialog 6 | from kivymd.color_definitions import colors 7 | from kivymd.uix.button import MDIconButton 8 | 9 | MY_PALETTE = ["DeepOrange", "Blue", "Purple"] 10 | 11 | KV = """ 12 | #: import colors kivymd.color_definitions.colors 13 | 14 | canvas: 15 | Color: 16 | rgba: root.rgb_hex(root.color_name) 17 | Ellipse: 18 | size: self.size 19 | pos: self.pos 20 | theme_text_color:'Custom' 21 | text_color: [0,0,0,0] 22 | 23 | 24 | on_release: 25 | app.primary_palette = root.color_name 26 | app.set_theme_style() 27 | 28 | 29 | cols: 5 30 | rows: 4 31 | adaptive_size: True 32 | spacing: "8dp" 33 | padding: dp(20) 34 | pos_hint: {"center_x": .5, "top": 1} 35 | 36 | """ 37 | Builder.load_string(KV) 38 | 39 | 40 | class ColorSelector(MDIconButton): 41 | color_name = OptionProperty("Indigo", options=MY_PALETTE) 42 | 43 | def rgb_hex(self, col): 44 | return get_color_from_hex(colors[col][self.theme_cls.primary_hue]) 45 | 46 | 47 | class MDThemePicker(AKAlertDialog): 48 | header_icon = "palette" 49 | 50 | def __init__(self, **kwargs): 51 | super().__init__(**kwargs) 52 | self.theme_cls.bind(theme_style=self.update_selector) 53 | self.content_cls = Factory.ColorGrid() 54 | self.header_font_size = "40dp" 55 | self.header_height_portrait = "90dp" 56 | self.update_bg_color() 57 | MDApp.get_running_app().bind(primary_palette=self.update_bg_color) 58 | self.size_portrait = ["320dp", "180dp"] 59 | 60 | def update_bg_color(self, *args): 61 | self.bg_color = MDApp.get_running_app().primary_accent 62 | 63 | def update_selector(self, *args): 64 | self.content_cls.clear_widgets() 65 | self.bg_color = MDApp.get_running_app().primary_accent 66 | 67 | def on_open(self): 68 | if not self.content_cls.children: 69 | for name_palette in MY_PALETTE: 70 | self.content_cls.add_widget( 71 | Factory.PrimaryColorSelector(color_name=name_palette) 72 | ) 73 | -------------------------------------------------------------------------------- /libs/save_config.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List 3 | 4 | from kivymd.app import MDApp 5 | 6 | app = MDApp.get_running_app() 7 | 8 | 9 | class SaveConfig: 10 | def __init__(self, *args: List[str]) -> None: 11 | self.variable_list = args 12 | self.variable_dict = {} 13 | 14 | def save_settings(self) -> None: 15 | for var in self.variable_list: 16 | exec(f"self.variable_dict['{var}']= app.{var}") 17 | with open("data/config.json", "w") as file: 18 | json.dump(self.variable_dict, file, indent=4) 19 | -------------------------------------------------------------------------------- /libs/screens/LoginScreen/LoginScreen.kv: -------------------------------------------------------------------------------- 1 | #: import platform kivy.platform 2 | #: import Clock kivy.clock.Clock 3 | #: import colors kivymd.color_definitions.colors 4 | #: import Window kivy.core.window.Window 5 | #: set height Window.height 6 | #: set width Window.width 7 | #: set rad width/2.4 8 | 9 | #: set primary_color_hex colors[app.theme_cls.primary_palette][app.theme_cls.primary_hue] 10 | 11 | : 12 | md_bg_color: app.bg_color 13 | on_enter: 14 | app.animate_signup('') 15 | canvas: 16 | Color: 17 | rgba:app.theme_cls.primary_color[:-1]+[.9] 18 | Ellipse: 19 | size:rad,rad 20 | pos:0-rad/2,height-rad+20 21 | angle_start:0 22 | angle_end:180 23 | Color: 24 | rgba:app.login_circle_light[:-1]+[.7] 25 | Ellipse: 26 | pos:0,height-rad/2 27 | size:rad,rad 28 | angle_start:90 29 | angle_end:270 30 | 31 | MDBoxLayout: 32 | adaptive_height:True 33 | id: box 34 | pos_hint:{"top":.95} 35 | orientation:'vertical' 36 | spacing:'70dp' 37 | MDLabel: 38 | adaptive_height:True 39 | text:f"LOGIN\nTO [color={primary_color_hex}][u]PASS••[/u]" 40 | markup:True 41 | valign:"center" 42 | theme_text_color:"Custom" 43 | text_color:app.text_color 44 | font_name:'Poppins' 45 | halign:'center' 46 | font_size:'44dp' 47 | pos_hint:{'top':1} 48 | MDBoxLayout: 49 | adaptive_height:True 50 | spacing:'50dp' 51 | orientation:'vertical' 52 | padding:(0,dp(15),0,0) 53 | MDBoxLayout: 54 | adaptive_height:True 55 | orientation:'vertical' 56 | spacing:'35dp' 57 | PasswordCard: 58 | id: password 59 | label_name:'Password' 60 | hint_text:"Enter Password" 61 | pos_hint:{'center_x':.5} 62 | on_text_validate: 63 | root.login_button_pressed(password.text) 64 | FloatingButton: 65 | id: lock 66 | icon:'login' 67 | on_press: 68 | root.login_button_pressed(password.text) 69 | pos_hint:{'center_x':.5} 70 | 71 | # MDTextButton: 72 | # id:forgot 73 | # x: password.x+dp(25) 74 | # y: password.y-dp(25) 75 | # text:'Forgot Password?' 76 | # font_name:'RobotoMedium' 77 | # theme_text_color:"Secondary" 78 | # font_size:"12sp" 79 | 80 | 81 | -------------------------------------------------------------------------------- /libs/screens/LoginScreen/LoginScreen.py: -------------------------------------------------------------------------------- 1 | from kivymd.app import MDApp 2 | from kivymd.toast import toast 3 | from kivymd.uix.screen import MDScreen 4 | import os.path 5 | from time import time 6 | from kivy.factory import Factory 7 | from kivy.core.window import Window 8 | from libs.screens.classes import SyncWidget 9 | from libs.utils import * 10 | 11 | app = MDApp.get_running_app() 12 | 13 | 14 | class LoginScreen(MDScreen): 15 | loading_view = None 16 | sync_widget = None 17 | logged_in = False 18 | password = None 19 | 20 | def get_sync_widget(self): 21 | if self.sync_widget is None: 22 | self.sync_widget = SyncWidget(pos_hint={"center_x":.8,"center_y":.1}) 23 | self.add_widget(self.sync_widget) 24 | return self.sync_widget 25 | 26 | def on_enter(self,*args): 27 | if app.auto_sync and not is_backup_failure(): 28 | self.sync_widget = self.get_sync_widget() 29 | app.restore(self.sync_widget, decrypt = False) 30 | 31 | def login_button_pressed(self, password): 32 | Window.softinput_mode = "below_target" 33 | def dismiss_spinner(*args): 34 | app.root.load_screen("HomeScreen") 35 | self.loading_view.dismiss() 36 | 37 | def initialise_encryption(): 38 | i = time() 39 | from libs.encryption import Encryption 40 | 41 | try: 42 | app.encryption_class = Encryption(password) 43 | if os.path.exists("data/passwords"): 44 | app.passwords = app.encryption_class.load_decrypted() 45 | else: 46 | if os.path.exists("data/encrypted_file.txt"): 47 | with open("data/encrypted_file.txt", "r") as f: 48 | app.encryption_class.decrypt(f.read()) 49 | else: 50 | print("'encrypted_file' not found, can't check password.") 51 | 52 | dismiss_spinner() 53 | if not self.password: 54 | self.password = password 55 | # app.root.HomeScreen.ids.create.ids.tab.switch_tab("[b]MANUAL") 56 | except UnicodeDecodeError: 57 | self.loading_view.dismiss() 58 | toast("Invalid password") 59 | # print(app.encrypted_keys) 60 | print(f"Time taken to load passwords = {time()-i}") 61 | if not self.password: 62 | if self.loading_view is None: 63 | self.loading_view = Factory.LoadingScreen() 64 | self.loading_view.open() 65 | self.loading_view.on_open = lambda *args: initialise_encryption() 66 | else: 67 | if self.password == password: 68 | app.root.load_screen("HomeScreen") 69 | else: 70 | toast("Invalid password") -------------------------------------------------------------------------------- /libs/screens/SettingsScreen/SettingsScreen.kv: -------------------------------------------------------------------------------- 1 | 2 | widget_style: "ios" 3 | 4 | 5 | ripple_alpha:.1 6 | text:"Auto sync" 7 | icon:"auto-upload" 8 | secondary_text:"Passwords will be synced" 9 | tertiary_text:'automatically' 10 | theme_text_color:'Custom' 11 | active: False 12 | text_color:app.text_color 13 | on_release: 14 | if self.right - self.last_touch.x >= dp(75): self.active = not self.active 15 | RightSwitch: 16 | id: switch 17 | active: root.active 18 | on_active: 19 | root.active = self.active 20 | width: dp(44) 21 | IconLeftWidget: 22 | icon: root.icon 23 | theme_text_color:'Custom' 24 | text_color:app.text_color 25 | 26 | 27 | theme_text_color:'Custom' 28 | text_color:app.text_color 29 | icon:"android" 30 | icon_color: [] 31 | size_hint_x:.85 32 | ripple_alpha:.1 33 | IconLeftWidget: 34 | icon: root.icon 35 | theme_text_color:'Custom' 36 | text_color:root.text_color if not root.icon_color else root.icon_color 37 | 38 | 39 | orientation:'vertical' 40 | MDLabel: 41 | text:'Made by Ashutosh' 42 | halign:'center' 43 | font_size:'20sp' 44 | size_hint_y:None 45 | height:dp(120) 46 | theme_text_color:'Custom' 47 | text_color: app.theme_cls.primary_color 48 | font_name:'Poppins' 49 | MDSeparator: 50 | MDTextButton: 51 | size_hint_y:.5 52 | markup: True 53 | font_size:'16sp' 54 | text:f"[font=Icons][size={int(self.font_size)+10}] {md_icons['github']}[/font][/size] GitHub Code" 55 | pos_hint:{'center_x':.5,'center_y':1} 56 | on_release: 57 | app.root.SettingsScreen.open_web(github=True) 58 | MDSeparator: 59 | MDTextButton: 60 | size_hint_y:.5 61 | markup: True 62 | font_size:'16sp' 63 | text:f"[color=#de6666][font=Icons][size={int(self.font_size)+10}] {md_icons['gmail']}[/font][/size] Contact Me[/color]" 64 | pos_hint:{'center_x':.5,'center_y':1} 65 | on_release: 66 | app.root.SettingsScreen.open_web(email=True) 67 | 68 | 69 | md_bg_color: app.bg_color 70 | BoxLayout: 71 | orientation:'vertical' 72 | Toolbar: 73 | title:'Settings' 74 | height:'70dp' 75 | md_bg_color:app.bg_color 76 | padding:dp(15),0 77 | icon_color:app.text_color 78 | font_style:"H5" 79 | left_action_items:[['arrow-left',lambda x: app.root.goback()]] 80 | ScrollView: 81 | MDBoxLayout: 82 | adaptive_height:True 83 | pos_hint:{"top":1} 84 | padding:0, dp(5) 85 | orientation:'vertical' 86 | spacing:'15dp' 87 | MDBoxLayout: 88 | adaptive_height:True 89 | orientation:'vertical' 90 | spacing:'5dp' 91 | MDBoxLayout: 92 | adaptive_height:True 93 | padding:dp(10),dp(5),dp(10),dp(10) 94 | MyListItem: 95 | icon:"account-circle" 96 | height: "58dp" 97 | bg_color: app.primary_accent 98 | text: app.email 99 | font_style:"H6" 100 | MyListItem: 101 | text:"Colors" 102 | icon:"circle" 103 | icon_color: app.theme_cls.primary_color 104 | on_release: 105 | root.change_colors() 106 | MyListItem: 107 | text:"Sync Passwords" 108 | icon:"sync" 109 | on_release: app.root.HomeScreen.open_sync_dialog() 110 | 111 | SwitchListItem: 112 | text:"Auto sync" 113 | secondary_text:"Passwords will be synced" 114 | tertiary_text:'automatically' 115 | active: app.auto_sync 116 | on_active: 117 | app.auto_sync = self.active 118 | 119 | SwitchListItem: 120 | id: system_dark_item 121 | text:"Use system theme" 122 | icon:"theme-light-dark" 123 | secondary_text:"Dark/Light mode of app will be" 124 | tertiary_text:"synchronised with system theme" 125 | active: app.system_dark_mode 126 | on_active: 127 | app.system_dark_mode = self.active 128 | 129 | SwitchListItem: 130 | text:"Extra Security" 131 | icon:"shield-star" if self.active else "shield-off-outline" 132 | secondary_text:"Login when you leave the app" 133 | tertiary_text: "for more than 5 minutes." 134 | active: app.extra_security 135 | on_active: 136 | app.extra_security = self.active 137 | 138 | 139 | MDSeparator: 140 | 141 | MDBoxLayout: 142 | adaptive_height:True 143 | orientation:'vertical' 144 | spacing:'5dp' 145 | MyListItem: 146 | text:"About the app" 147 | icon:"information" 148 | on_release: 149 | root.open_about() 150 | MyListItem: 151 | text:"YouTube Demo" 152 | icon:"youtube" 153 | icon_color:[.8,0,0,1] 154 | on_release: 155 | root.open_web(youtube=True) 156 | 157 | MyListItem: 158 | text:"Log out" 159 | icon:"logout" 160 | disabled: app.signup 161 | size_hint_x:.5 162 | pos_hint:{'center_x':.5} 163 | opacity: 1 * (not self.disabled) 164 | on_release: 165 | root.logout() 166 | 167 | 168 | -------------------------------------------------------------------------------- /libs/screens/SettingsScreen/SettingsScreen.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | from kivy.core.clipboard import Clipboard 4 | from kivy.factory import Factory 5 | from kivy.clock import Clock 6 | from kivymd .toast import toast 7 | from kivymd.uix.screen import MDScreen 8 | from libs.modules.picker import MDThemePicker 9 | from libs.utils import remove_user_data 10 | from kivymd.app import MDApp 11 | from libs.modules.dialogs import AKAlertDialog 12 | 13 | app = MDApp.get_running_app() 14 | 15 | 16 | class SettingsScreen(MDScreen): 17 | content = None 18 | theme_picker = None 19 | YOUTUBE_VIDEO_LINK = "https://www.youtube.com/watch?v=EOkMDc5mZWI&list=PLUdItSprD91ybWz6uxs4zF4Gux_vzdbZh&index=1" 20 | GITHUB_REPO_LINK = "https://github.com/AM-ash-OR-AM-I/Passlock" 21 | 22 | def logout(self): 23 | app.root.load_screen("SignupScreen", empty_history=True) 24 | app.root.SignupScreen.on_enter = lambda *args: Clock.schedule_once( 25 | lambda x: app.animate_signup(app.root.SignupScreen.ids.box), 0 26 | ) 27 | remove_user_data() 28 | 29 | def change_colors(self): 30 | if self.theme_picker is None: 31 | self.theme_picker = MDThemePicker() 32 | self.theme_picker.open() 33 | 34 | def open_about(self): 35 | if self.content is None: 36 | self.content = Factory.AboutClass() 37 | self.about_dialog = AKAlertDialog(header_icon='heart-circle') 38 | self.about_dialog.header_height_portrait = "90dp" 39 | self.about_dialog.size_portrait = ['300dp', '340dp'] 40 | self.about_dialog.content_cls = self.content 41 | self.about_dialog.bg_color = app.primary_accent 42 | self.about_dialog.open() 43 | 44 | def open_web(self, github=False, youtube=False, email=False): 45 | if github: 46 | webbrowser.open(self.GITHUB_REPO_LINK) 47 | toast('Star my repository if you like it :)') 48 | elif youtube: 49 | webbrowser.open(self.YOUTUBE_VIDEO_LINK) 50 | elif email: 51 | webbrowser.open('https://mail.google.com/mail/u/0/#inbox?compose=new') 52 | Clipboard.copy('ashutoshmaha2909@gmail.com') 53 | toast('Email address Copied, Paste Email address to send email.') 54 | 55 | def open_youtube_demo(self): 56 | if self.YOUTUBE_VIDEO_LINK: 57 | webbrowser.open(self.YOUTUBE_VIDEO_LINK) 58 | -------------------------------------------------------------------------------- /libs/screens/SignupScreen/SignupScreen.kv: -------------------------------------------------------------------------------- 1 | #: import platform kivy.platform 2 | #: import Clock kivy.clock.Clock 3 | #: import colors kivymd.color_definitions.colors 4 | #: import Window kivy.core.window.Window 5 | #: set height Window.height 6 | #: set width Window.width 7 | #: set rad width/2 8 | #: set lbl_size '18sp' 9 | 10 | #: set primary_color_hex colors[app.theme_cls.primary_palette][app.theme_cls.primary_hue] 11 | 12 | : 13 | md_bg_color: app.bg_color 14 | canvas: 15 | Color: 16 | rgba:app.theme_cls.primary_color[:-1]+[.9] 17 | Ellipse: 18 | size:rad,rad 19 | pos:0-rad/2,height-rad+20 20 | angle_start:0 21 | angle_end:180 22 | Color: 23 | rgba:app.login_circle_light[:-1]+[.7] 24 | Ellipse: 25 | pos:0,height-rad/2 26 | size:rad,rad 27 | angle_start:90 28 | angle_end:270 29 | 30 | MDBoxLayout: 31 | adaptive_height:True 32 | id: box 33 | pos_hint:{"top":0.95} 34 | orientation:'vertical' 35 | spacing:'50dp' 36 | MDLabel: 37 | adaptive_height:True 38 | text:("SIGN UP\n" if root.show_signup else "LOGIN\n") + f"TO [color={primary_color_hex}][u]PASS••[/u]" 39 | markup:True 40 | theme_text_color:"Custom" 41 | text_color:app.text_color 42 | font_name:'Poppins' 43 | halign:'center' 44 | font_size:'44dp' 45 | pos_hint:{'top':1} 46 | MDBoxLayout: 47 | adaptive_height:True 48 | spacing:'50dp' 49 | orientation:'vertical' 50 | padding:(0,dp(15),0,0) 51 | BorderCard: 52 | id: email 53 | label_name:'Email Address' 54 | hint_text:'username@mail.com' if root.show_signup else "Your mail name" 55 | pos_hint:{'center_x':.5} 56 | MDBoxLayout: 57 | adaptive_height:True 58 | orientation:'vertical' 59 | spacing:'35dp' 60 | PasswordCard: 61 | id: password 62 | label_name:'Password' 63 | hint_text:('Create' if root.show_signup else "Enter")+" Password" 64 | pos_hint:{'center_x':.5} 65 | on_text_validate: 66 | root.button_pressed(email.text, password.text) 67 | FloatingButton: 68 | id: lock 69 | icon:'login' if not root.show_signup else "lock" 70 | on_release: 71 | root.button_pressed(email.text, password.text) 72 | pos_hint:{'center_x':.5} 73 | 74 | MDSeparator: 75 | id: separator 76 | opacity: 1 77 | size_hint_x:.8 78 | y:lock.y - dp(55) 79 | pos_hint:{"center_x":.5} 80 | 81 | MDTextButton: 82 | id: switch_signin 83 | opacity: 1 84 | text:f'New user? [color={primary_color_hex}]Signup!'if not root.show_signup else f"Existing user? [color={primary_color_hex}]Login." 85 | markup:True 86 | top:separator.y - dp(20) 87 | pos_hint:{'center_x':.5} 88 | font_name:'RobotoMedium' 89 | theme_text_color:"Custom" 90 | text_color:app.text_color 91 | halign:'center' 92 | size_hint_x:1 93 | on_release: root.show_signup = not root.show_signup 94 | font_size:lbl_size 95 | 96 | 97 | -------------------------------------------------------------------------------- /libs/screens/SignupScreen/SignupScreen.py: -------------------------------------------------------------------------------- 1 | from email import message 2 | from typing import Dict 3 | from kivymd.app import MDApp 4 | from kivymd.toast import toast 5 | from kivymd.uix.screen import MDScreen 6 | import threading 7 | from time import time 8 | from kivy.properties import BooleanProperty 9 | from kivy.factory import Factory 10 | 11 | from libs.firebase import Firebase 12 | from libs.screens.classes import SyncWidget 13 | 14 | app = MDApp.get_running_app() 15 | 16 | 17 | class SignupScreen(MDScreen): 18 | loading_view = None 19 | show_signup = BooleanProperty(True) 20 | encryption = None 21 | 22 | def on_show_signup(self, *args): 23 | """Animation to be shown when clicking on login or signup""" 24 | 25 | print("switch_signup") 26 | box = self.ids.box 27 | box.pos_hint = {"top": 0.8} 28 | box.opacity = 0 29 | app.animate_signup(box) 30 | 31 | def save_uid_password(self, uid, email): 32 | """ 33 | Saves user_id and a file that has been encrypted with master password. 34 | This makes sure that even when user hasn't created any passwords, 35 | app can still verify the password. 36 | """ 37 | 38 | with open("data/user_id.txt", "w") as f: 39 | f.write(uid) 40 | with open("data/email.txt", "w") as f: 41 | f.write(email) 42 | app.email = email 43 | with open("data/encrypted_file.txt", "w") as f: 44 | f.write(app.encryption_class.encrypt("Test")) 45 | 46 | def dismiss_loading(self, *args): 47 | app.root.load_screen("HomeScreen") 48 | self.loading_view.dismiss() 49 | 50 | def signup(self, email, password): 51 | def signup_success(req, result): 52 | user_id = result["localId"] 53 | toast("Signup successful") 54 | app.encryption_class = self.encryption(self.password) 55 | self.dismiss_loading() 56 | threading.Thread(target=self.save_uid_password, args=(user_id, email)).start() 57 | 58 | def signup_failure(req, result): 59 | message = result["error"]["message"] 60 | print(message) 61 | if message == "EMAIL_EXISTS": 62 | toast("Email already exists, attempting to login instead.") 63 | self.login(email, password) 64 | else: 65 | message = message.replace("_", " ").capitalize() 66 | toast(message) 67 | self.loading_view.dismiss() 68 | 69 | self.firebase.signup_success = lambda req, result: signup_success(req, result) 70 | self.firebase.signup_failure = lambda req, result: signup_failure(req, result) 71 | self.firebase.signup(email, password) 72 | 73 | def login(self, email, password): 74 | def login_success(req, result): 75 | user_id = result["localId"] 76 | toast("Login successful") 77 | self.dismiss_loading() 78 | app.encryption_class = self.encryption(self.password) 79 | app.root.HomeScreen.restore(user_id=user_id) 80 | threading.Thread(target=self.save_uid_password, args=(user_id,email)).start() 81 | 82 | def login_failure(req, result): 83 | message = result["error"]["message"] 84 | message = message.replace("_", " ").capitalize() 85 | toast(message) 86 | self.loading_view.dismiss() 87 | 88 | self.firebase.login_success = lambda req, result: login_success(req, result) 89 | self.firebase.login_failure = lambda req, result: login_failure(req, result) 90 | self.firebase.login(email, password) 91 | app.root.load_screen("HomeScreen", set_current=False) 92 | 93 | def button_pressed(self, email, password): 94 | def import_encryption(): 95 | from libs.encryption import Encryption 96 | 97 | self.encryption = Encryption 98 | if not self.show_signup: 99 | self.login(email, password) 100 | 101 | self.email = email 102 | self.password = password 103 | self.firebase = Firebase() 104 | 105 | if self.loading_view is None: 106 | self.loading_view = Factory.LoadingScreen() 107 | self.loading_view.text = ( 108 | "Signing up..." if self.show_signup else "Logging in..." 109 | ) 110 | self.loading_view.open() 111 | self.loading_view.on_open = lambda *args: import_encryption() 112 | if self.show_signup: 113 | self.signup(email, password) 114 | -------------------------------------------------------------------------------- /libs/screens/root.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from kivy import platform 3 | from kivy.core.window import Window 4 | from kivy.lang import Builder 5 | from kivy.properties import ListProperty 6 | from kivy.uix.screenmanager import ScreenManager, CardTransition 7 | from kivymd.toast import toast 8 | from kivymd.app import MDApp 9 | 10 | 11 | class Root(ScreenManager): 12 | _prev_press = None 13 | history = ListProperty() 14 | 15 | def __init__(self, **kwargs): 16 | super().__init__(**kwargs) 17 | Window.bind(on_keyboard=self._handle_keyboard) 18 | self.transition = CardTransition(duration=0.3) 19 | 20 | def load_screen( 21 | self, 22 | screen_name, 23 | side="left", 24 | _from_goback=False, 25 | set_current=True, 26 | empty_history=False, 27 | ): 28 | # checks that the screen already added to the screen-manager 29 | if not self.has_screen(screen_name): 30 | # loads the kv file 31 | Builder.load_file(f"libs/screens/{screen_name}/{screen_name}.kv") 32 | # imports the screen class dynamically 33 | exec(f"from libs.screens.{screen_name}.{screen_name} import {screen_name}") 34 | # calls the screen class to get the instance of it 35 | self.screen_object = eval(f"{screen_name}()") 36 | # automatically sets the screen name using the arg that passed in set_current 37 | self.screen_object.name = screen_name 38 | # saves screen instance object to access later. 39 | exec(f"self.{screen_name} = self.screen_object") 40 | # finnaly adds the screen to the screen-manager 41 | self.add_widget(self.screen_object) 42 | 43 | # saves screen information to history 44 | # if you not want a screen to go back 45 | # use like below 46 | # if not from_goback and screen_name not in ["auth", ...] 47 | 48 | if not _from_goback and set_current: 49 | self.transition.mode = "push" 50 | self.transition.direction = "left" 51 | self.history.append(screen_name) 52 | 53 | # sets transition direction 54 | # sets the current screen 55 | if set_current: 56 | self.current = screen_name 57 | if empty_history: 58 | self.history = [] 59 | 60 | def check_press_back_twice(self): 61 | def show_toast(): 62 | if platform == "android": 63 | toast("Press back again to close the app", length_long=False) 64 | else: 65 | toast("Press back again to close the app", duration=.6) 66 | 67 | self._press_again = time() 68 | if self._prev_press: 69 | if (self._press_again - self._prev_press) < 2: 70 | MDApp.get_running_app().stop() 71 | else: 72 | show_toast() 73 | else: 74 | show_toast() 75 | self._prev_press = time() 76 | 77 | def _handle_keyboard(self, instance, key, *args): 78 | if key == 27: 79 | if ( 80 | self.current == "HomeScreen" 81 | and self.current_screen.ids.tab_manager.current == "FindScreen" 82 | ): 83 | self.current_screen.ids.tab_manager.current = "CreateScreen" 84 | elif self.current == "SettingsScreen": 85 | self.goback() 86 | else: 87 | self.check_press_back_twice() 88 | return True 89 | 90 | def goback(self): 91 | if len(self.history) > 1: 92 | self.history.pop() 93 | prev_screen = self.history[-1] 94 | self.transition.mode = "pop" 95 | self.transition.direction = "right" 96 | self.load_screen(prev_screen, _from_goback=True) 97 | -------------------------------------------------------------------------------- /libs/utils.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import pickle 3 | import json 4 | import string 5 | import random 6 | 7 | if not os.path.exists("data"): 8 | os.mkdir("data") 9 | 10 | 11 | def auto_password(length: int, ascii=True, digits=True, special_chars=True) -> str: 12 | universe = "" 13 | password = "" 14 | if ascii: 15 | password += random.choice(string.ascii_letters) 16 | universe += string.ascii_letters 17 | if digits: 18 | universe += string.digits 19 | password += random.choice(string.digits) 20 | if special_chars: 21 | universe += string.punctuation 22 | password += random.choice(string.punctuation) 23 | 24 | rest = length - len(password) 25 | if universe == "": # if all options are false 26 | return "Error: No options selected" 27 | for _ in range(rest): 28 | password += random.choice(universe) 29 | 30 | password = list(password) 31 | random.shuffle(password) 32 | password = "".join(password) 33 | return password 34 | 35 | 36 | def load_passwords() -> dict: 37 | if os.path.exists("data/passwords"): 38 | with open("data/passwords", "rb") as f: 39 | encrypted_pass = pickle.load(f) 40 | return encrypted_pass 41 | else: 42 | return {} 43 | 44 | 45 | def get_uid() -> str: 46 | with open("data/user_id.txt", "r") as f: 47 | uid = f.read() 48 | return uid 49 | 50 | 51 | def write_passwords(dictionary: dict) -> None: 52 | with open("data/passwords", "wb") as f: 53 | pickle.dump(dictionary, f) 54 | 55 | 56 | def remove_user_data() -> None: 57 | if os.path.exists("data/user_id.txt"): 58 | os.remove("data/user_id.txt") 59 | if os.path.exists("data/passwords"): 60 | os.remove("data/passwords") 61 | if os.path.exists("data/encrypted_file.txt"): 62 | os.remove("data/encrypted_file.txt") 63 | 64 | 65 | def _get_config() -> dict: 66 | if os.path.exists("data/config.json"): 67 | with open("data/config.json", "r") as f: 68 | config = json.load(f) 69 | return config 70 | else: 71 | return {} 72 | 73 | 74 | def get_email() -> str: 75 | if os.path.exists("data/email.txt"): 76 | with open("data/email.txt", "r") as f: 77 | email = f.read() 78 | return email 79 | else: 80 | return "DemoMail" 81 | 82 | 83 | def get_primary_palette() -> str: 84 | if os.path.exists("data/config.json"): 85 | with open("data/config.json", "r") as f: 86 | config = json.load(f) 87 | return config.get("primary_palette", "DeepOrange") 88 | else: 89 | return "DeepOrange" 90 | 91 | 92 | def is_dark_mode(system=False) -> bool: 93 | json_data = _get_config() 94 | return json_data.get("system_dark_mode", True) if system else json_data.get("dark_mode", False) 95 | 96 | 97 | def is_backup_failure() -> bool: 98 | _json_file = _get_config() 99 | return _json_file.get("backup_failure", False) 100 | 101 | 102 | def is_extra_security() -> bool: 103 | _json_file = _get_config() 104 | return _json_file.get("extra_security", False) 105 | 106 | 107 | def check_auto_sync() -> bool: 108 | json_data = _get_config() 109 | return json_data.get("auto_sync", True) 110 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | kivy 2 | pillow 3 | pycryptodome 4 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM-ash-OR-AM-I/Passlock/0232719654997770309bcdd8c84be8ff1dcce500/screenshots/6.png --------------------------------------------------------------------------------