├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── Using KivyMD widgets.py ├── banner example.py ├── button example 2.py ├── button example1.py ├── simple Example.py └── slider example.py ├── images ├── buttons1.png ├── buttons2.png ├── demo.png └── slider.png ├── neukivy ├── __init__.py ├── app.py ├── factory_registers.py ├── font_definitions.py ├── fonts │ ├── NunitoSans-Black.ttf │ ├── NunitoSans-BlackItalic.ttf │ ├── NunitoSans-Bold.ttf │ ├── NunitoSans-BoldItalic.ttf │ ├── NunitoSans-ExtraBold.ttf │ ├── NunitoSans-ExtraBoldItalic.ttf │ ├── NunitoSans-ExtraLight.ttf │ ├── NunitoSans-ExtraLightItalic.ttf │ ├── NunitoSans-Italic.ttf │ ├── NunitoSans-Light.ttf │ ├── NunitoSans-LightItalic.ttf │ ├── NunitoSans-Regular.ttf │ ├── NunitoSans-SemiBold.ttf │ ├── NunitoSans-SemiBoldItalic.ttf │ └── materialdesignicons-webfont.ttf ├── icon_definitions.py ├── kivymdconfig.py ├── tools │ ├── __init__.py │ └── colorconvertor.py └── uix │ ├── __init__.py │ ├── banner.py │ ├── behaviors │ ├── __init__.py │ ├── neubuttonbehavior.py │ ├── neuglow.py │ ├── neumorph.py │ └── themeablebehavior.py │ ├── button.py │ ├── card.py │ └── slider.py └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.py[cod] 3 | *$py.class 4 | .Python 5 | lib 6 | lib64 7 | parts 8 | sdist 9 | var 10 | wheels 11 | pip-wheel-metadata 12 | share/python-wheels 13 | develop-eggs 14 | eggs 15 | .eggs 16 | *.egg-info 17 | *.egg 18 | MANIFEST 19 | .installed.cfg 20 | downloads 21 | docs/_build 22 | build 23 | dist 24 | bin 25 | .buildozer 26 | cache 27 | .cache 28 | temp 29 | .temp 30 | .pytest_cache 31 | .coverage 32 | .DS_Store 33 | test.py 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Guhan Sensam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeuKivy 2 | 3 | ![demo](https://github.com/Guhan-SenSam/NeuKivy/blob/main/images/demo.png) 4 | 5 | Neukivy is a collection of neumorphic widgets built with Kivy. The library is currently in its initial development so there isn't much yet. But hopefully it will grow into a library you can use to easily create neumorphic UI in python. 6 | 7 | ## How to Install 8 | 1. Neukivy requires the latest version of pillow to run. This dependency will eventually be removed. 9 | ``` 10 | python3 -m pip install --upgrade Pillow 11 | ``` 12 | Make sure you are running the latest version (8.2.0) 13 | 14 | 2. Now install NeuKivy 15 | ``` 16 | pip install --upgrade git+git://github.com/Guhan-SenSam/NeuKivy.git 17 | ``` 18 | 19 | ## Support 20 | [Discord](https://discord.com/channels/943755643741933618/943789595055779850) 21 | ## Usage 22 | 23 | There is a temporary examples directory that contains some code to help you understand the basic properties of NeuKivy better. 24 | 25 | ## Things to Know 26 | 27 | 1. I am just starting! NeuKivy is very young and still needs to grow by a lot to become of any practical use. Right now its more of a proof of concept and a demo rather than an actual usable library. 28 | 29 | 2. All the properties have doc strings explaining what they do. 30 | 31 | 3. I would love for your feedback and any ideas that you have for NeuKivy! 32 | 33 | ## PERFORMANCE PROBLEMS 34 | 35 | NeuKivy needs to compute two shadows for every widget that is displayed. A shadow's computation is slow due to the dependency of Pillow. Thus the library has been structured in a way so as to minimize the amount of needed shadow recomputes. As of now when a widget changes size or elevation the shadow is recomputed. 36 | 37 | On a PC the impact is negligible but on Android, animations regarding widget sizes is very slow. It might be okay with a couple of small widgets. But anything more will cause frame drops. 38 | 39 | Elevation changes do not affect performance as much, but also should not be animated as much as possible. 40 | 41 | Currently work is being done to draw the shadows using glsl shaders. This would make redrawing the shadows very fast. 42 | 43 | **If you know anything about glsl shaders and creating super fast drop shadow shaders, your contributions are greatly welcome.(As I am not that experienced in glsl and it will take a long time to learn the language and the techniques.)** 44 | 45 | To sum everything up. NeuKivy can run on android as long as you don't animate a lot of widget sizes ;) 46 | 47 | # Widgets Available 48 | 1. Button 49 | - Rectangular 50 | - Rounded Rectangle 51 | - Circular 52 | 53 | 2. Icon Button 54 | - Rectangular 55 | - Rounded Rectangle 56 | - Circular 57 | 58 | ![buttons1](https://github.com/Guhan-SenSam/NeuKivy/blob/main/images/buttons1.png) 59 | 3. Icon With Text Button 60 | 61 | (Can choose which side icon appears) 62 | - Rectangular 63 | - Rounded Rectangle 64 | 65 | ![buttons2](https://github.com/Guhan-SenSam/NeuKivy/blob/main/images/buttons2.png) 66 | 67 | 4. Card 68 | 69 | 5. Banner 70 | 71 | 6. Sliders 72 | ![sliders](https://github.com/Guhan-SenSam/NeuKivy/blob/main/images/slider.png) 73 | -------------------------------------------------------------------------------- /examples/Using KivyMD widgets.py: -------------------------------------------------------------------------------- 1 | from neukivy.app import NeuApp 2 | from kivy.lang import Builder 3 | from kivy.animation import Animation 4 | 5 | kv_string = """ 6 | Screen: 7 | canvas: 8 | Color: 9 | rgba:app.theme_manager._bg_color_alp 10 | Rectangle: 11 | size:self.size 12 | pos:self.pos 13 | 14 | MDCard: 15 | md_bg_color:app.theme_manager._bg_color_alp 16 | elevation:20 17 | size_hint:None,None 18 | size:500,200 19 | pos_hint:{'center_x':.5,'center_y':.5} 20 | radius:20,20,20,20 21 | FloatLayout: 22 | size:self.parent.size 23 | pos:self.parent.size 24 | 25 | NeuCircularIconButton: 26 | id:button 27 | pos_hint:{'center_x':.7,'center_y':.5} 28 | size:100,100 29 | icon:'account-alert' 30 | font_size:'40sp' 31 | 32 | NeuCircularButton: 33 | pos_hint:{'center_x':.3,'center_y':.5} 34 | text:'NeuKivy' 35 | radius:100 36 | down_elevation:1 37 | up_elevation:3 38 | font_size:'20sp' 39 | 40 | MDFloatingActionButton: 41 | text:'From KivyMD' 42 | pos_hint:{'center_x':.5,'center_y':.25} 43 | icon:'plus' 44 | md_bg_color:self.theme_cls.accent_color 45 | 46 | MDRaisedButton: 47 | text:'From KivyMD' 48 | pos_hint:{'center_x':.5,'center_y':.1} 49 | icon:'plus' 50 | md_bg_color:self.theme_cls.accent_color 51 | 52 | 53 | 54 | """ 55 | 56 | 57 | class MainApp(NeuApp): 58 | def build(self): 59 | # If set to True this will allow you to load kivymd widgets into NeuKivy 60 | # Check dosctrings for NeuApp class for more info 61 | self.use_kivymd = True 62 | kv = Builder.load_string(kv_string) 63 | # Set the app colors at start. 64 | # The bg_color property should not have an alpha value. This is auto computed 65 | self.theme_manager.bg_color = (0.2, 0.2, 0.2) 66 | # Set this to a lighter shade of your bg_color 67 | self.theme_manager.light_color = (0.3, 0.3, 0.3, 1) 68 | # Set this to a darker shade of your bg_color 69 | self.theme_manager.dark_color = (0.07, 0.07, 0.07, 1) 70 | # The text color of your app 71 | self.theme_manager.text_color = (0.5, 0.4, 0.2, 1) 72 | # Disabled text color of your app 73 | self.theme_manager.disabled_text_color = (0.1, 0.1, 0.1, 1) 74 | return kv 75 | 76 | 77 | if __name__ == "__main__": 78 | MainApp().run() 79 | -------------------------------------------------------------------------------- /examples/banner example.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.core.window import Window 3 | from kivy.lang import Builder 4 | from kivy.uix.boxlayout import BoxLayout 5 | from neukivy.app import NeuApp 6 | 7 | kv_string = """ 8 | Screen: 9 | canvas: 10 | Color: 11 | rgba:app.theme_manager._bg_color_alp 12 | Rectangle: 13 | size:self.size 14 | pos:self.pos 15 | NeuBanner: 16 | id:banner 17 | primary_text:"Hello this is a top banner." 18 | secondary_text:"This is the text that goes underneath it." 19 | tertiary_text:"This is the final text." 20 | over_widget:screen 21 | elevation:3 22 | 23 | NeuBannerAction: 24 | MDRaisedButton: 25 | text:'okay' 26 | 27 | NeuBannerAction: 28 | MDRaisedButton: 29 | text:'cancel' 30 | 31 | GridLayout: 32 | id:screen 33 | cols:2 34 | rows:2 35 | spacing:'40dp' 36 | padding:'40dp','40dp','40dp','40dp' 37 | 38 | NeuRoundedIconButton: 39 | icon:'plus' 40 | size_hint:.5,.5 41 | on_release:root.ids.banner.show() 42 | 43 | NeuRoundedIconButton: 44 | icon:'plus' 45 | size_hint:.5,.5 46 | on_release:root.ids.banner.show() 47 | 48 | NeuRoundedIconButton: 49 | icon:'plus' 50 | size_hint:.5,.5 51 | on_release:root.ids.banner.show() 52 | 53 | NeuRoundedIconButton: 54 | icon:'plus' 55 | size_hint:.5,.5 56 | on_release:root.ids.banner.show() 57 | 58 | 59 | 60 | 61 | """ 62 | 63 | 64 | class MainApp(NeuApp): 65 | def build(self): 66 | self.use_kivymd = True 67 | kv = Builder.load_string(kv_string) 68 | self.theme_manager.bg_color = (0.2, 0.2, 0.2) 69 | self.theme_manager.light_color = (0.3, 0.3, 0.3, 1) 70 | self.theme_manager.dark_color = (0.07, 0.07, 0.07, 1) 71 | return kv 72 | 73 | 74 | Window.size = (360, 640) 75 | 76 | if __name__ == "__main__": 77 | MainApp().run() 78 | -------------------------------------------------------------------------------- /examples/button example 2.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.lang import Builder 3 | from kivy.uix.boxlayout import BoxLayout 4 | from neukivy.app import NeuApp 5 | from kivy.core.window import Window 6 | 7 | kv_string = """ 8 | Screen: 9 | canvas: 10 | Color: 11 | rgba:app.theme_manager._bg_color_alp 12 | Rectangle: 13 | size:self.size 14 | pos:self.pos 15 | 16 | Label: 17 | size:self.size 18 | size_hint:None,None 19 | font_size:'40sp' 20 | text:'NeuKivy Buttons' 21 | pos_hint:{'center_y':.9, 'center_x':.5} 22 | color:app.theme_manager.text_color 23 | 24 | 25 | GridLayout: 26 | cols:4 27 | size:self.minimum_size 28 | padding:'40dp','40dp','40dp','40dp' 29 | pos_hint:{'center_y':.5} 30 | 31 | BoxLayout: 32 | orientation:'vertical' 33 | spacing:'20dp' 34 | NeuIconTextButton: 35 | pos_hint:{'center_x':.5} 36 | up_elevation:2 37 | text:'NeuKivy' 38 | icon:'plus' 39 | size:150,150 40 | icon_pos:'right' 41 | Label: 42 | text:'NeuButton' 43 | size:self.texture_size 44 | size_hint:None,None 45 | pos_hint:{'center_x':.5} 46 | color:app.theme_manager.text_color 47 | BoxLayout: 48 | orientation:'vertical' 49 | spacing:'20dp' 50 | NeuIconTextButton: 51 | pos_hint:{'center_x':.5} 52 | up_elevation:2 53 | text:'NeuKivy' 54 | icon:'plus' 55 | size:150,150 56 | icon_pos:'left' 57 | Label: 58 | text:'NeuButton' 59 | size:self.texture_size 60 | size_hint:None,None 61 | pos_hint:{'center_x':.5} 62 | color:app.theme_manager.text_color 63 | 64 | BoxLayout: 65 | orientation:'vertical' 66 | spacing:'20dp' 67 | NeuIconTextButton: 68 | pos_hint:{'center_x':.5} 69 | up_elevation:2 70 | text:'NeuKivy' 71 | icon:'plus' 72 | size:150,150 73 | icon_pos:'top' 74 | Label: 75 | text:'NeuButton' 76 | size:self.texture_size 77 | size_hint:None,None 78 | pos_hint:{'center_x':.5} 79 | color:app.theme_manager.text_color 80 | BoxLayout: 81 | orientation:'vertical' 82 | spacing:'20dp' 83 | NeuIconTextButton: 84 | pos_hint:{'center_x':.5} 85 | up_elevation:2 86 | text:'NeuKivy' 87 | icon:'plus' 88 | size:150,150 89 | icon_pos:'bottom' 90 | Label: 91 | text:'NeuButton' 92 | size:self.texture_size 93 | size_hint:None,None 94 | pos_hint:{'center_x':.5} 95 | color:app.theme_manager.text_color 96 | 97 | BoxLayout: 98 | orientation:'vertical' 99 | spacing:'20dp' 100 | NeuRoundedIconTextButton: 101 | pos_hint:{'center_x':.5} 102 | up_elevation:2 103 | text:'NeuKivy' 104 | icon:'plus' 105 | size:150,150 106 | icon_pos:'right' 107 | Label: 108 | text:'NeuButton' 109 | size:self.texture_size 110 | size_hint:None,None 111 | pos_hint:{'center_x':.5} 112 | color:app.theme_manager.text_color 113 | 114 | BoxLayout: 115 | orientation:'vertical' 116 | spacing:'20dp' 117 | NeuRoundedIconTextButton: 118 | pos_hint:{'center_x':.5} 119 | up_elevation:2 120 | text:'NeuKivy' 121 | icon:'plus' 122 | size:150,150 123 | icon_pos:'left' 124 | Label: 125 | text:'NeuButton' 126 | size:self.texture_size 127 | size_hint:None,None 128 | pos_hint:{'center_x':.5} 129 | color:app.theme_manager.text_color 130 | 131 | BoxLayout: 132 | orientation:'vertical' 133 | spacing:'20dp' 134 | NeuRoundedIconTextButton: 135 | pos_hint:{'center_x':.5} 136 | up_elevation:2 137 | text:'NeuKivy' 138 | icon:'plus' 139 | size:150,150 140 | icon_pos:'bottom' 141 | Label: 142 | text:'NeuButton' 143 | size:self.texture_size 144 | size_hint:None,None 145 | pos_hint:{'center_x':.5} 146 | color:app.theme_manager.text_color 147 | 148 | 149 | BoxLayout: 150 | orientation:'vertical' 151 | spacing:'20dp' 152 | NeuRoundedIconTextButton: 153 | pos_hint:{'center_x':.5} 154 | up_elevation:2 155 | text:'NeuKivy' 156 | icon:'plus' 157 | size:150,150 158 | icon_pos:'top' 159 | Label: 160 | text:'NeuButton' 161 | size:self.texture_size 162 | size_hint:None,None 163 | pos_hint:{'center_x':.5} 164 | color:app.theme_manager.text_color 165 | 166 | 167 | """ 168 | 169 | 170 | class MainApp(NeuApp): 171 | def build(self): 172 | self.use_kivymd = True 173 | kv = Builder.load_string(kv_string) 174 | self.theme_manager.bg_color = (0.8, 0.8, 0.85) 175 | self.theme_manager.light_color = (0.9, 0.9, 0.95, 1) 176 | self.theme_manager.dark_color = (0.6, 0.6, 0.65, 1) 177 | self.theme_manager.text_color = (0.5, 0.2, 0.9, 1) 178 | return kv 179 | 180 | 181 | if __name__ == "__main__": 182 | MainApp().run() 183 | -------------------------------------------------------------------------------- /examples/button example1.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.lang import Builder 3 | from kivy.uix.boxlayout import BoxLayout 4 | from neukivy.app import NeuApp 5 | from kivy.core.window import Window 6 | 7 | kv_string = """ 8 | Screen: 9 | canvas: 10 | Color: 11 | rgba:app.theme_manager._bg_color_alp 12 | Rectangle: 13 | size:self.size 14 | pos:self.pos 15 | 16 | Label: 17 | size:self.size 18 | size_hint:None,None 19 | font_size:'40sp' 20 | text:'NeuKivy Buttons' 21 | pos_hint:{'center_y':.9, 'center_x':.5} 22 | color:app.theme_manager.text_color 23 | 24 | 25 | GridLayout: 26 | cols:3 27 | size:self.minimum_size 28 | padding:'40dp','40dp','40dp','40dp' 29 | pos_hint:{'center_y':.5} 30 | 31 | BoxLayout: 32 | orientation:'vertical' 33 | spacing:'20dp' 34 | NeuButton: 35 | pos_hint:{'center_x':.5} 36 | up_elevation:2 37 | text:'NeuKivy' 38 | size:150,150 39 | Label: 40 | text:'NeuButton' 41 | size:self.texture_size 42 | size_hint:None,None 43 | pos_hint:{'center_x':.5} 44 | color:app.theme_manager.text_color 45 | BoxLayout: 46 | orientation:'vertical' 47 | spacing:'20dp' 48 | NeuRoundedButton: 49 | pos_hint:{'center_x':.5} 50 | up_elevation:2 51 | text:'NeuKivy' 52 | size:150,150 53 | Label: 54 | text:'NeuRoundedButton' 55 | size:self.texture_size 56 | size_hint:None,None 57 | pos_hint:{'center_x':.5} 58 | color:app.theme_manager.text_color 59 | 60 | BoxLayout: 61 | orientation:'vertical' 62 | spacing:'20dp' 63 | NeuCircularButton: 64 | pos_hint:{'center_x':.5} 65 | up_elevation:2 66 | text:'NeuKivy' 67 | radius:150 68 | Label: 69 | text:'NeuCircularButton' 70 | size:self.texture_size 71 | size_hint:None,None 72 | pos_hint:{'center_x':.5} 73 | color:app.theme_manager.text_color 74 | 75 | BoxLayout: 76 | orientation:'vertical' 77 | spacing:'20dp' 78 | NeuIconButton: 79 | pos_hint:{'center_x':.5} 80 | up_elevation:2 81 | icon:'cog' 82 | size:150,150 83 | font_size:'25sp' 84 | Label: 85 | text:'NeuIconButton' 86 | size:self.texture_size 87 | size_hint:None,None 88 | pos_hint:{'center_x':.5} 89 | color:app.theme_manager.text_color 90 | 91 | BoxLayout: 92 | orientation:'vertical' 93 | spacing:'20dp' 94 | NeuRoundedIconButton: 95 | pos_hint:{'center_x':.5} 96 | up_elevation:2 97 | icon:'cog' 98 | size:150,150 99 | font_size:'25sp' 100 | Label: 101 | text:'NeuRoundedIconButton' 102 | size:self.texture_size 103 | size_hint:None,None 104 | pos_hint:{'center_x':.5} 105 | color:app.theme_manager.text_color 106 | 107 | BoxLayout: 108 | orientation:'vertical' 109 | spacing:'20dp' 110 | NeuCircularIconButton: 111 | pos_hint:{'center_x':.5} 112 | up_elevation:2 113 | icon:'cog' 114 | radius:150 115 | font_size:'25sp' 116 | Label: 117 | text:'NeuCircularIconButton' 118 | size:self.texture_size 119 | size_hint:None,None 120 | pos_hint:{'center_x':.5} 121 | color:app.theme_manager.text_color 122 | 123 | 124 | """ 125 | 126 | 127 | class MainApp(NeuApp): 128 | def build(self): 129 | self.use_kivymd = True 130 | kv = Builder.load_string(kv_string) 131 | self.theme_manager.bg_color = (0.8, 0.8, 0.85) 132 | self.theme_manager.light_color = (0.9, 0.9, 0.95, 1) 133 | self.theme_manager.dark_color = (0.6, 0.6, 0.65, 1) 134 | self.theme_manager.text_color = (0.5, 0.2, 0.9, 1) 135 | return kv 136 | 137 | 138 | if __name__ == "__main__": 139 | MainApp().run() 140 | -------------------------------------------------------------------------------- /examples/simple Example.py: -------------------------------------------------------------------------------- 1 | from neukivy.app import NeuApp 2 | from kivy.lang import Builder 3 | from kivy.animation import Animation 4 | 5 | kv_string = """ 6 | Screen: 7 | canvas: 8 | Color: 9 | rgba:app.theme_manager._bg_color_alp 10 | Rectangle: 11 | size:self.size 12 | pos:self.pos 13 | NeuCircularIconButton: 14 | id:button 15 | pos_hint:{'center_x':.7,'center_y':.5} 16 | size:100,100 17 | icon:'account-alert' 18 | font_size:'40sp' 19 | NeuCircularButton: 20 | pos_hint:{'center_x':.3,'center_y':.5} 21 | text:'NeuKivy' 22 | radius:200 23 | down_elevation:1 24 | up_elevation:3 25 | font_size:'20sp' 26 | """ 27 | 28 | 29 | class MainApp(NeuApp): 30 | def build(self): 31 | kv = Builder.load_string(kv_string) 32 | # Set the app colors at start. 33 | # The bg_color property should not have an alpha value. This is auto computed 34 | self.theme_manager.bg_color = (0.2, 0.2, 0.2) 35 | # Set this to a lighter shade of your bg_color 36 | self.theme_manager.light_color = (0.3, 0.3, 0.3, 1) 37 | # Set this to a darker shade of your bg_color 38 | self.theme_manager.dark_color = (0.07, 0.07, 0.07, 1) 39 | # The text color of your app 40 | self.theme_manager.text_color = (0.5, 0.4, 0.2, 1) 41 | # Disabled text color of your app 42 | self.theme_manager.disabled_text_color = (0.1, 0.1, 0.1, 1) 43 | return kv 44 | 45 | 46 | if __name__ == "__main__": 47 | MainApp().run() 48 | -------------------------------------------------------------------------------- /examples/slider example.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.core.window import Window 3 | from kivy.lang import Builder 4 | from kivy.uix.boxlayout import BoxLayout 5 | from neukivy.app import NeuApp 6 | 7 | kv_string = """ 8 | Screen: 9 | canvas: 10 | Color: 11 | rgba:app.theme_manager._bg_color_alp 12 | Rectangle: 13 | size:self.size 14 | pos:self.pos 15 | 16 | Label: 17 | text:'NeuSlider' 18 | pos_hint:{'center_x':.5,'center_y':.94} 19 | font_size:'30sp' 20 | font_name:'NunitoLight' 21 | 22 | NeuSlider: 23 | pos_hint:{'center_x':.5,'center_y':.8} 24 | elevation:2 25 | border_width:10 26 | thumb_color:0.9,.3,.3,1 27 | 28 | NeuSlider: 29 | pos_hint:{'center_x':.5,'center_y':.6} 30 | elevation:-2 31 | border_width:10 32 | thumb_color:0.8,.4,.1,1 33 | 34 | NeuSlider: 35 | pos_hint:{'center_x':.5,'center_y':.4} 36 | elevation:-2 37 | border_width:10 38 | thumb_color:0.2,0.9,.2,1 39 | thumb_padding:20 40 | 41 | NeuSlider: 42 | pos_hint:{'center_x':.5,'center_y':.2} 43 | elevation:-2 44 | border_width:5 45 | thumb_color:0.4,.4,.9,1 46 | height:dp(20) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | """ 55 | 56 | 57 | class MainApp(NeuApp): 58 | def build(self): 59 | self.use_kivymd = True 60 | self.kv = Builder.load_string(kv_string) 61 | self.theme_manager.bg_color = (0.2, 0.2, 0.2) 62 | self.theme_manager.light_color = (0.3, 0.3, 0.3, 1) 63 | self.theme_manager.dark_color = (0.1, 0.1, 0.1, 1) 64 | self.theme_manager.text_color = (0.5, 0.2, 0.9, 1) 65 | return self.kv 66 | 67 | 68 | if __name__ == "__main__": 69 | MainApp().run() 70 | -------------------------------------------------------------------------------- /images/buttons1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/images/buttons1.png -------------------------------------------------------------------------------- /images/buttons2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/images/buttons2.png -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/images/demo.png -------------------------------------------------------------------------------- /images/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/images/slider.png -------------------------------------------------------------------------------- /neukivy/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import kivy 3 | 4 | 5 | kivy.require("2.0.0") 6 | 7 | path = os.path.dirname(__file__) 8 | """Path to NeuKivy package directory.""" 9 | 10 | fonts_path = os.path.join(path, f"fonts{os.sep}") 11 | """Path to fonts directory.""" 12 | 13 | 14 | import neukivy.factory_registers # NOQA 15 | import neukivy.font_definitions # NOQA 16 | -------------------------------------------------------------------------------- /neukivy/app.py: -------------------------------------------------------------------------------- 1 | from kivy.app import App 2 | from kivy.event import EventDispatcher 3 | from kivy.logger import Logger 4 | from kivy.properties import BooleanProperty, ColorProperty, ListProperty, ObjectProperty 5 | from neukivy.kivymdconfig import factory_register 6 | from neukivy.uix.behaviors.themeablebehavior import ThemeableBehavior 7 | 8 | 9 | class Theme_Manger(EventDispatcher): 10 | 11 | bg_color = ListProperty([0, 0, 0]) 12 | """ 13 | Background Color of the app. Only RGB channels to be defined. 14 | The alpha channel is determined internally for each component. 15 | 16 | :attr:`bg_color` is an :class:`~kivy.properties.ColorProperty` 17 | and defaults to `[0,0,0]`. 18 | """ 19 | 20 | _bg_color_alp = ColorProperty([0, 0, 0, 0]) 21 | """ 22 | Background Color of the app with alpha channel as `1`. This property is used 23 | internally and is read only. Changing it will lead to unexpected behavior. 24 | 25 | attr:`bg_color` is an :class:`~kivy.properties.ColorProperty` 26 | and defaults to `[0,0,0,0]`. 27 | """ 28 | 29 | _bg_color_noalp = ColorProperty([0, 0, 0, 0]) 30 | """ 31 | Background Color of the app with alpha channel as `0`. This property is used 32 | internally and is read only. Changing it will lead to unexpected beahvior. 33 | 34 | """ 35 | 36 | light_color = ColorProperty([0, 0, 0, 0]) 37 | """ 38 | Color of the lighter shadow for a widget. 39 | 40 | attr:`light_color` is an :class:`~kivy.properties.ColorProperty` 41 | and defaults to `[0,0,0,0]`. 42 | """ 43 | 44 | dark_color = ColorProperty([0, 0, 0, 0]) 45 | """ 46 | Color of the darker shadow for a widget. 47 | 48 | attr:`dark_color` is an :class:`~kivy.properties.ColorProperty` 49 | and defaults to `[0,0,0,0]`. 50 | """ 51 | 52 | primary_color = ColorProperty([0, 0, 0, 0]) 53 | """ 54 | The primary color of your app. Will be used in various parts of the ui to add 55 | contrast 56 | 57 | attr:`primary_color` is an :class:`~kivy.properties.ColorProperty` 58 | and defaults to `[0,0,0,0]`. 59 | """ 60 | 61 | text_color = ColorProperty([0, 0, 0, 0]) 62 | """ 63 | Color of text used in the app. 64 | 65 | attr:`text_color` is an :class:`~kivy.properties.ColorProperty` 66 | and defaults to `[1,1,1,1]`. 67 | """ 68 | 69 | disabled_text_color = ColorProperty([0.2, 0.2, 0.2, 1]) 70 | """ 71 | Color of text if a widget has been disabled 72 | 73 | attr:`disabled_text_color` is an :class:`~kivy.properties.ColorProperty` 74 | and defaults to `[0.2, 0.2, 0.2, 1]`. 75 | """ 76 | 77 | def __init__(self, **kwargs): 78 | super().__init__(**kwargs) 79 | 80 | def on_bg_color(self, *args): 81 | if len(self.bg_color) > 3: 82 | Logger.info( 83 | "NeuKivy:'bg_color' alpha channel cannot be set. Ignoring provided alpha channel value" 84 | ) 85 | self._bg_color_noalp = [self.bg_color[0], self.bg_color[1], self.bg_color[2], 0] 86 | self._bg_color_alp = [self.bg_color[0], self.bg_color[1], self.bg_color[2], 1] 87 | 88 | 89 | class NeuApp(App): 90 | 91 | theme_manager = ObjectProperty() 92 | """ 93 | Instance of `ThemeableBehavior` that can be used to set app default colors. 94 | These color values will be set to all widgets provided they do not already have 95 | their own color properties set. In which case the custom color properties will be 96 | used for that widget alone 97 | """ 98 | 99 | use_kivymd = BooleanProperty(False) 100 | """ 101 | If set to `True` you can use KivyMD widgets inside NeuKivy. This is useful as the entire ui 102 | of an app cannot consist of neumorphic widgets. This property enables you to use material 103 | design widgets provided in KivyMD. 104 | Check KivyMD documentation here 105 | (https://kivymd.readthedocs.io/en/latest/) 106 | """ 107 | 108 | theme_cls = ObjectProperty() 109 | """ 110 | This property is used in order to allow KivyMD widgets to work within NeuKivy. 111 | It allows you to access all the color definitions and theme support available 112 | inside KivyMD. Remember `theme_manager` is used to set colors for all NeuKivy widgets 113 | whereas the colors for KivyMD widgets are controlled by the `theme_cls`. 114 | """ 115 | 116 | def __init__(self, **kwargs): 117 | super().__init__(**kwargs) 118 | self.theme_manager = Theme_Manger() 119 | 120 | def on_use_kivymd(self, *args): 121 | if self.use_kivymd: 122 | # Check to see if kivymd is installed 123 | try: 124 | import kivymd 125 | except ImportError: 126 | 127 | raise ImportError( 128 | """Please install kivymd before using it in your application""" 129 | ) 130 | factory_register() 131 | from kivymd.theming import ThemeManager 132 | 133 | self.theme_cls = ThemeManager() 134 | -------------------------------------------------------------------------------- /neukivy/factory_registers.py: -------------------------------------------------------------------------------- 1 | from kivy.factory import Factory 2 | 3 | r = Factory.register 4 | 5 | r("NeuButton", module="neukivy.uix.button") 6 | r("NeuRoundedButton", module="neukivy.uix.button") 7 | r("NeuCircularButton", module="neukivy.uix.button") 8 | r("NeuIconButton", module="neukivy.uix.button") 9 | r("NeuRoundedIconButton", module="neukivy.uix.button") 10 | r("NeuCircularIconButton", module="neukivy.uix.button") 11 | r("NeuIconTextButton", module="neukivy.uix.button") 12 | r("NeuRoundedIconTextButton", module="neukivy.uix.button") 13 | r("NeuCard", module="neukivy.uix.card") 14 | r("NeuBackdrop", module="neukivy.uix.backdrop") 15 | r("NeuBackdropFrontLayer", module="neukivy.uix.backdrop") 16 | r("NeuBanner", module="neukivy.uix.banner") 17 | r("NeuBannerAction", module="neukivy.uix.banner") 18 | r("NeuSlider", module="neukivy.uix.slider") 19 | -------------------------------------------------------------------------------- /neukivy/font_definitions.py: -------------------------------------------------------------------------------- 1 | from kivy.core.text import LabelBase 2 | from neukivy import fonts_path 3 | 4 | fonts = [ 5 | { 6 | "name": "Nunito", 7 | "fn_regular": fonts_path + "NunitoSans-Regular.ttf", 8 | "fn_bold": fonts_path + "NunitoSans-Bold.ttf", 9 | "fn_italic": fonts_path + "NunitoSans-Italic.ttf", 10 | "fn_bolditalic": fonts_path + "NunitoSans-BoldItalic.ttf", 11 | }, 12 | { 13 | "name": "NunitoExtraLight", 14 | "fn_regular": fonts_path + "NunitoSans-ExtraLight.ttf", 15 | "fn_italic": fonts_path + "NunitoSans-ExtraLightItalic.ttf", 16 | }, 17 | { 18 | "name": "NunitoLight", 19 | "fn_regular": fonts_path + "NunitoSans-Light.ttf", 20 | "fn_italic": fonts_path + "NunitoSans-LightItalic.ttf", 21 | }, 22 | { 23 | "name": "NunitoSemiBold", 24 | "fn_regular": fonts_path + "NunitoSans-SemiBold.ttf", 25 | "fn_italic": fonts_path + "NunitoSans-SemiBoldItalic.ttf", 26 | }, 27 | { 28 | "name": "NunitoExtraBold", 29 | "fn_regular": fonts_path + "NunitoSans-ExtraBold.ttf", 30 | "fn_italic": fonts_path + "NunitoSans-ExtraBoldItalic.ttf", 31 | }, 32 | { 33 | "name": "NunitoBlack", 34 | "fn_regular": fonts_path + "NunitoSans-Black.ttf", 35 | "fn_italic": fonts_path + "NunitoSans-BlackItalic.ttf", 36 | }, 37 | { 38 | "name": "Icons", 39 | "fn_regular": fonts_path + "materialdesignicons-webfont.ttf", 40 | }, 41 | ] 42 | 43 | for font in fonts: 44 | LabelBase.register(**font) 45 | -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-Black.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-BlackItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-Bold.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-BoldItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-ExtraBold.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-ExtraLight.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-Italic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-Light.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-LightItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-Regular.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-SemiBold.ttf -------------------------------------------------------------------------------- /neukivy/fonts/NunitoSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/NunitoSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /neukivy/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /neukivy/kivymdconfig.py: -------------------------------------------------------------------------------- 1 | """ 2 | Register KivyMD widgets to use without import 3 | """ 4 | 5 | from kivy.factory import Factory 6 | from kivy.app import App 7 | 8 | def factory_register(): 9 | r = Factory.register 10 | # r("MDStepper", module="kivymd.uix.stepper") 11 | r("MDNavigationRail", module="kivymd.uix.navigationrail") 12 | r("MDSwiper", module="kivymd.uix.swiper") 13 | r("MDCarousel", module="kivymd.uix.carousel") 14 | r("MDFloatLayout", module="kivymd.uix.floatlayout") 15 | r("MDScreen", module="kivymd.uix.screen") 16 | r("MDBoxLayout", module="kivymd.uix.boxlayout") 17 | r("MDRelativeLayout", module="kivymd.uix.relativelayout") 18 | r("MDGridLayout", module="kivymd.uix.gridlayout") 19 | r("MDStackLayout", module="kivymd.uix.stacklayout") 20 | r("MDExpansionPanel", module="kivymd.uix.expansionpanel") 21 | r("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") 22 | r("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") 23 | r("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") 24 | r("FitImage", module="kivymd.utils.fitimage") 25 | r("MDBackdrop", module="kivymd.uix.backdrop") 26 | r("MDBanner", module="kivymd.uix.banner") 27 | r("MDTooltip", module="kivymd.uix.tooltip") 28 | r("MDBottomNavigation", module="kivymd.uix.bottomnavigation") 29 | r("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") 30 | r("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior") 31 | r("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button") 32 | r("MDIconButton", module="kivymd.uix.button") 33 | r("MDRoundImageButton", module="kivymd.uix.button") 34 | r("MDFlatButton", module="kivymd.uix.button") 35 | r("MDRaisedButton", module="kivymd.uix.button") 36 | r("MDFloatingActionButton", module="kivymd.uix.button") 37 | r("MDRectangleFlatButton", module="kivymd.uix.button") 38 | r("MDTextButton", module="kivymd.uix.button") 39 | r("MDCustomRoundIconButton", module="kivymd.uix.button") 40 | r("MDRoundFlatButton", module="kivymd.uix.button") 41 | r("MDFillRoundFlatButton", module="kivymd.uix.button") 42 | r("MDRectangleFlatIconButton", module="kivymd.uix.button") 43 | r("MDRoundFlatIconButton", module="kivymd.uix.button") 44 | r("MDFillRoundFlatIconButton", module="kivymd.uix.button") 45 | r("MDCard", module="kivymd.uix.card") 46 | r("MDSeparator", module="kivymd.uix.card") 47 | r("MDSelectionList", module="kivymd.uix.selection") 48 | r("MDChip", module="kivymd.uix.chip") 49 | r("MDChooseChip", module="kivymd.uix.chip") 50 | r("SmartTile", module="kivymd.uix.imagelist") 51 | r("SmartTileWithLabel", module="kivymd.uix.imagelist") 52 | r("SmartTileWithStar", module="kivymd.uix.imagelist") 53 | r("MDLabel", module="kivymd.uix.label") 54 | r("MDIcon", module="kivymd.uix.label") 55 | r("MDList", module="kivymd.uix.list") 56 | r("ILeftBody", module="kivymd.uix.list") 57 | r("ILeftBodyTouch", module="kivymd.uix.list") 58 | r("IRightBody", module="kivymd.uix.list") 59 | r("IRightBodyTouch", module="kivymd.uix.list") 60 | r("ContainerSupport", module="kivymd.uix.list") 61 | r("OneLineListItem", module="kivymd.uix.list") 62 | r("TwoLineListItem", module="kivymd.uix.list") 63 | r("ThreeLineListItem", module="kivymd.uix.list") 64 | r("OneLineAvatarListItem", module="kivymd.uix.list") 65 | r("TwoLineAvatarListItem", module="kivymd.uix.list") 66 | r("ThreeLineAvatarListItem", module="kivymd.uix.list") 67 | r("OneLineIconListItem", module="kivymd.uix.list") 68 | r("TwoLineIconListItem", module="kivymd.uix.list") 69 | r("ThreeLineIconListItem", module="kivymd.uix.list") 70 | r("OneLineRightIconListItem", module="kivymd.uix.list") 71 | r("TwoLineRightIconListItem", module="kivymd.uix.list") 72 | r("ThreeLineRightIconListItem", module="kivymd.uix.list") 73 | r("OneLineAvatarIconListItem", module="kivymd.uix.list") 74 | r("TwoLineAvatarIconListItem", module="kivymd.uix.list") 75 | r("ThreeLineAvatarIconListItem", module="kivymd.uix.list") 76 | r("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") 77 | r("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior") 78 | r("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior") 79 | r("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") 80 | r("MDNavigationLayout", module="kivymd.uix.navigationdrawer") 81 | r("MDProgressBar", module="kivymd.uix.progressbar") 82 | r("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") 83 | r("MDCheckbox", module="kivymd.uix.selectioncontrol") 84 | r("MDSwitch", module="kivymd.uix.selectioncontrol") 85 | r("MDSlider", module="kivymd.uix.slider") 86 | r("MDSpinner", module="kivymd.uix.spinner") 87 | r("MDTabs", module="kivymd.uix.tab") 88 | r("MDTextField", module="kivymd.uix.textfield") 89 | r("MDTextFieldRound", module="kivymd.uix.textfield") 90 | r("MDTextFieldRect", module="kivymd.uix.textfield") 91 | r("MDToolbar", module="kivymd.uix.toolbar") 92 | r("MDBottomAppBar", module="kivymd.uix.toolbar") 93 | r("MDDropDownItem", module="kivymd.uix.dropdownitem") 94 | r("MDCircularLayout", module="kivymd.uix.circularlayout") 95 | -------------------------------------------------------------------------------- /neukivy/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/tools/__init__.py -------------------------------------------------------------------------------- /neukivy/tools/colorconvertor.py: -------------------------------------------------------------------------------- 1 | def rgb_2_dec(color): 2 | return [channel / 255 for channel in color] 3 | 4 | 5 | def dec_2_rgb(color): 6 | return [int(channel * 255) for channel in color] 7 | -------------------------------------------------------------------------------- /neukivy/uix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guhan-SenSam/NeuKivy/ed200893b8f997691d611d90a85cef261ad4fe26/neukivy/uix/__init__.py -------------------------------------------------------------------------------- /neukivy/uix/banner.py: -------------------------------------------------------------------------------- 1 | from kivy.animation import Animation 2 | from kivy.lang import Builder 3 | from kivy.metrics import dp 4 | from kivy.properties import ( 5 | BooleanProperty, 6 | ColorProperty, 7 | ListProperty, 8 | NumericProperty, 9 | ObjectProperty, 10 | StringProperty, 11 | ) 12 | from kivy.uix.boxlayout import BoxLayout 13 | from neukivy.uix.behaviors.themeablebehavior import ThemeableBehavior 14 | from neukivy.uix.card import NeuCard 15 | 16 | Builder.load_string( 17 | """ 18 | #:import Window kivy.core.window.Window 19 | : 20 | size_hint:1,None 21 | height:self.minimum_height 22 | y: Window.height - self.height 23 | 24 | NeuCard: 25 | id:card 26 | size_hint:1,None 27 | height:self.minimum_height 28 | orientation: "vertical" 29 | spacing: "10dp" 30 | padding:10,10,10,10 31 | elevation:root._elevation 32 | border_width:root.border_width 33 | comp_color:root.comp_color 34 | light_color:root.light_color 35 | dark_color:root.dark_color 36 | 37 | Label: 38 | id: text 39 | text_size: self.parent.width-dp(20), None 40 | size: self.texture_size 41 | size_hint:None,None 42 | text:root.text 43 | pos_hint:{'center_x':.5} 44 | font_name: root.font_name if root.font_name else 'NunitoBlack' 45 | color:root.text_color 46 | 47 | 48 | BoxLayout: 49 | id:container 50 | size_hint: None, None 51 | size: self.minimum_size 52 | pos_hint: {"right": 1} 53 | padding: 0, 0, "10dp",0 54 | spacing: "10dp" 55 | 56 | 57 | : 58 | size_hint: None, None 59 | size: self.minimum_size 60 | pos_hint:{'center_y':.5} 61 | 62 | """ 63 | ) 64 | 65 | 66 | class NeuBanner(BoxLayout, ThemeableBehavior): 67 | 68 | text = StringProperty("Banner") 69 | """ 70 | Text to be displayed in the banner 71 | 72 | attr:`text` is an :class:`~kivy.properties.StringProperty` 73 | and defaults to `''`. 74 | """ 75 | 76 | text_color = ListProperty([0, 0, 0, 0]) 77 | """ 78 | Color of the text 79 | 80 | attr:`text` is an :class:`~kivy.properties.StringProperty` 81 | and defaults to `''`. 82 | """ 83 | 84 | font_name = StringProperty() 85 | """ 86 | 87 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 88 | and defaults to `'NunitoBlack'`. 89 | """ 90 | 91 | banner_height = NumericProperty("100dp") 92 | """ 93 | Banner height 94 | 95 | attr:`banner_height` is an :class:`~kivy.properties.NumericProperty` 96 | and defaults to `100dp`. 97 | """ 98 | 99 | padding = ListProperty([dp(20), dp(20), dp(20), dp(20)]) 100 | """ 101 | Padding of the BoxLayout that is holding the Banner Card. This property 102 | will control how much gap should be given around the card to ensure that 103 | the neumorphic shadow effect is visible 104 | 105 | attr:`padding` is an :class:`~kivy.properties.ListProperty` 106 | and defaults to `[dp(20), dp(20), dp(20), dp(20)]`. 107 | """ 108 | 109 | over_widget = ObjectProperty() 110 | """ 111 | Widget that the banner is above. Set this property to the widget that is directly 112 | underneath the banner. This widget will be moved down when the banner is shown. 113 | 114 | attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty` 115 | and defaults to None. 116 | """ 117 | 118 | elevation = NumericProperty(3) 119 | """ 120 | Elevation of the banner. Hint: The widget will look better with a small positive elevation 121 | 122 | attr:`elevation` is an :class:`~kivy.properties.NumericProperty` 123 | and defaults to `3`. 124 | """ 125 | 126 | border_width = NumericProperty(10) 127 | """ 128 | Border width for negative elevation 129 | 130 | attr:`border_width` is an :class:`~kivy.properties.NumericProperty` 131 | and defaults to `10`. 132 | """ 133 | 134 | duration = NumericProperty(0.3) 135 | """ 136 | Duration of the animations 137 | 138 | attr:`duration` is an :class:`~kivy.properties.NumericProperty` 139 | and defaults to `0.3`. 140 | """ 141 | 142 | interpolation = StringProperty("in_out_circ") 143 | """ 144 | Interpolation to be used in all animations 145 | 146 | attr:`interpolation` is an :class:`~kivy.properties.StringProperty` 147 | and defaults to `"in_out_circ"`. 148 | """ 149 | 150 | shown = BooleanProperty(False) 151 | """ 152 | Read only property that depicts if the banner is shown or not 153 | 154 | attr:`shown` is an :class:`~kivy.properties.BooleanProperty` 155 | and defaults to `False`. 156 | """ 157 | 158 | opacity = NumericProperty(0) 159 | """ 160 | Opacity of the banner. Do not chnage this property as it is set internally to show 161 | and close the banner. 162 | """ 163 | 164 | comp_color = ListProperty([0, 0, 0, 0]) 165 | 166 | dark_color = ListProperty([0, 0, 0, 0]) 167 | 168 | light_color = ListProperty([0, 0, 0, 0]) 169 | 170 | _elevation = NumericProperty(0) 171 | 172 | def __init__(self, **kwargs): 173 | super().__init__(**kwargs) 174 | 175 | def add_widget(self, widget, *args): 176 | if widget.__class__ is NeuBannerAction: 177 | self.ids.container.add_widget(widget) 178 | else: 179 | return super().add_widget(widget) 180 | 181 | def show(self, *args): 182 | if not self.shown: 183 | anim = Animation( 184 | y=self.over_widget.y - self.height, 185 | duration=self.duration, 186 | t=self.interpolation, 187 | ) 188 | anim.start(self.over_widget) 189 | anim.bind(on_complete=self.show_animation) 190 | 191 | def show_animation(self, *args): 192 | self.shown = True 193 | self.opacity = 1 194 | anim = Animation( 195 | _elevation=self.elevation, duration=self.duration, t=self.interpolation 196 | ) 197 | anim.start(self) 198 | 199 | def close(self, *args): 200 | if self.shown: 201 | anim = Animation(_elevation=0, duration=self.duration, t=self.interpolation) 202 | anim.start(self) 203 | anim.bind(on_complete=self.close_animation) 204 | 205 | def close_animation(self, *args): 206 | self.shown = False 207 | anim = Animation( 208 | y=self.over_widget.y + self.height, 209 | duration=self.duration, 210 | t=self.interpolation, 211 | ) 212 | anim.start(self.over_widget) 213 | Animation( 214 | opacity=0, 215 | duration=self.duration, 216 | t=self.interpolation, 217 | ).start(self) 218 | 219 | def on_touch_up(self, touch): 220 | if self.ids.container.collide_point(*touch.pos) and self.shown: 221 | pass 222 | elif self.ids.card.collide_point(*touch.pos) and self.shown: 223 | self.close() 224 | 225 | 226 | class NeuBannerAction(BoxLayout): 227 | pass 228 | -------------------------------------------------------------------------------- /neukivy/uix/behaviors/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /neukivy/uix/behaviors/neubuttonbehavior.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.properties import BooleanProperty, NumericProperty 3 | 4 | 5 | class NeuButtonBehavior: 6 | 7 | up_elevation = NumericProperty(3) 8 | """ 9 | Elevation level when the button is in the up state. 10 | 11 | attr:`up_elevation` is an :class:`~kivy.properties.NumericProperty` 12 | and defaults to `3`. 13 | """ 14 | 15 | down_elevation = NumericProperty(1) 16 | """ 17 | Elevation level when the button is in the down state. 18 | 19 | attr:`down_elevation` is an :class:`~kivy.properties.NumericProperty` 20 | and defaults to `1`. 21 | """ 22 | 23 | pressed = BooleanProperty(False) 24 | """ 25 | Read only property that represents if the button is pressed or not. 26 | 27 | attr:`pressed` is an :class:`~kivy.properties.BooleanProperty` 28 | and defaults to `False`. 29 | """ 30 | 31 | do_text_shrink = BooleanProperty(True) 32 | """ 33 | When set to `True`, text on a button will shrink when the button is pressed. 34 | This is done to mimic the effect that the text is going into the screen on press. 35 | The amount of change in font size can be adjusted using the property `text_shrink_amount`. 36 | 37 | If you have created a custom widget and want to use this effect in a widget. Set the `id` 38 | value of your text(which is inside the widget) to `label`. The effect will apply to to any 39 | label with id = 'label'. 40 | 41 | attr:`do_text_shrink` is an :class:`~kivy.properties.BooleanProperty` 42 | and defaults to `True`. 43 | """ 44 | 45 | text_shrink_amount = NumericProperty(1) 46 | """ 47 | Amount to decrease the font_size on button press. Will only work if `do_text_shrink` is `True`. 48 | You can try setting this to a negative value to make the text shrink. 49 | If `text_shrink_amount = 1` then the font_size will decrease by '1sp'. 50 | 51 | attr:`text_shrink_amount` is an :class:`~kivy.properties.NumericProperty` 52 | and defaults to `1`. 53 | """ 54 | 55 | disabled = BooleanProperty(False) 56 | """ 57 | Whether the widget has been disabled or not 58 | 59 | attr:`disabled` is an :class:`~kivy.properties.BooleanProperty` 60 | and defaults to `False`. 61 | """ 62 | 63 | def __init__(self, **kwargs): 64 | super().__init__(**kwargs) 65 | self.register_event_type("on_press") 66 | self.register_event_type("on_release") 67 | Clock.schedule_once(self.elevation_setter, 0) 68 | 69 | def elevation_setter(self, *args): 70 | self.elev = self.up_elevation 71 | 72 | def on_touch_down(self, touch): 73 | if self.collide_point(*touch.pos) and not self.disabled: 74 | self.pressed = True 75 | self.elev = self.down_elevation 76 | self.dispatch("on_press") 77 | if "label" in self.ids and self.do_text_shrink: 78 | self.ids.label.font_size = ( 79 | self.ids.label.font_size - self.text_shrink_amount 80 | ) 81 | 82 | def on_touch_up(self, touch): 83 | if self.pressed and not self.disabled: 84 | self.elev = self.up_elevation 85 | self.pressed = False 86 | self.dispatch("on_release") 87 | if "label" in self.ids and self.do_text_shrink: 88 | self.ids.label.font_size = ( 89 | self.ids.label.font_size + self.text_shrink_amount 90 | ) 91 | 92 | def on_press(self, *args): 93 | pass 94 | 95 | def on_release(self, *args): 96 | pass 97 | -------------------------------------------------------------------------------- /neukivy/uix/behaviors/neuglow.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.graphics.texture import Texture 3 | from kivy.properties import ColorProperty, ListProperty, NumericProperty, ObjectProperty 4 | from neukivy.tools.colorconvertor import dec_2_rgb 5 | from PIL import Image, ImageDraw, ImageFilter 6 | 7 | 8 | class NeuGlowCircular: 9 | 10 | glow_radius = NumericProperty(0) 11 | """ 12 | Radius of the glow 13 | 14 | attr:`glow_radius` is an :class:`~kivy.properties.NumericProperty` 15 | and defaults to `0`. 16 | """ 17 | 18 | color = ColorProperty([0, 0, 0, 0]) 19 | """ 20 | Color of the glow. It is suggested to set this property to the color of the 21 | component that the glow is being added to 22 | 23 | attr:`color` is an :class:`~kivy.properties.ColorProperty` 24 | and defaults to `[0, 0, 0, 0]`. 25 | """ 26 | 27 | behind_color = ColorProperty([0, 0, 0, 0]) 28 | """ 29 | Color of the component behind the glow. If left blank it will result in 30 | a black ring around your glow, so make sure you set this color properly 31 | 32 | attr:`behind_color` is an :class:`~kivy.properties.ColorProperty` 33 | and defaults to `[0, 0, 0, 0]`. 34 | """ 35 | 36 | glow_texture = ObjectProperty() 37 | """ 38 | The property that holds the actual glow texture object 39 | 40 | """ 41 | 42 | glow_size = ListProperty([0, 0]) 43 | """ 44 | A list containing the size of the glow texture 45 | """ 46 | 47 | glow_pos = ListProperty([0, 0]) 48 | """ 49 | A list containing the position of the glow texture 50 | """ 51 | 52 | def __init__(self, *args, **kwargs): 53 | super(NeuGlowCircular, self).__init__(*args, **kwargs) 54 | self.blank_texture = Texture.create(size=self.size, colorfmt="rgba") 55 | Clock.schedule_once(self._create_glow) 56 | 57 | def _create_glow(self, *args): 58 | # Create blank image 59 | blank_x_size = int(self.size[0] + self.glow_radius * 2) 60 | blank_y_size = int(self.size[1] + self.glow_radius * 2) 61 | shadow = Image.new( 62 | "RGBA", 63 | (blank_x_size, blank_y_size), 64 | color=(tuple(dec_2_rgb(self.behind_color))), 65 | ) 66 | # Convert to drawable image 67 | blank_draw = ImageDraw.Draw(shadow) 68 | x0, y0 = (blank_x_size - self.size[0]) / 2.0, ( 69 | blank_y_size - self.size[1] 70 | ) / 2.0 71 | x1, y1 = x0 + self.size[0], y0 + self.size[1] 72 | blank_draw.ellipse( 73 | [(x0, y0), (x1, y1)], 74 | fill=tuple(tuple(dec_2_rgb(self.glow_color))), 75 | ) 76 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 77 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.glow_radius / 2)) 78 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 79 | self.glow_texture = texture 80 | self.glow_size = (blank_x_size, blank_y_size) 81 | self.glow_pos = self.pos[0] - x0, self.pos[1] - y0 82 | 83 | def _update_glow_pos(self): 84 | self.glow_pos = self.pos[0] - self.glow_radius, self.pos[1] - self.glow_radius 85 | 86 | def on_size(self, *args): 87 | self._create_glow() 88 | 89 | def on_pos(self, *args): 90 | self._update_glow_pos() 91 | 92 | def on_glow_radius(self, *args): 93 | self._create_glow() 94 | -------------------------------------------------------------------------------- /neukivy/uix/behaviors/neumorph.py: -------------------------------------------------------------------------------- 1 | from kivy.graphics.texture import Texture 2 | from kivy.properties import ListProperty, NumericProperty, ObjectProperty 3 | from neukivy.tools.colorconvertor import dec_2_rgb 4 | from PIL import Image, ImageDraw, ImageFilter 5 | 6 | 7 | class NeuMorphRectangle: 8 | """ 9 | This class is used to create the neumorphic shadows for widgets. Currently the 10 | method used is quite slow if there are large number of widgets on screen or if 11 | a widget is very big. This is especially apparent on android devices. 12 | 13 | This will eventually be fixed in the future, so for now you can create neumorphic ui 14 | on a pc. Just dont go too crazy unless you want a slideshow as your app. 15 | 16 | To create the neumorphic effect we basically create two shadows for each widget. 17 | One shadow is darker than the widget color and one is lighter. These two shadows are 18 | then positioned diagonally opposite of each of other to give the illusion of 3D. 19 | 20 | For positiive elevation we just blit these textures to the canvas.before and then 21 | draw our widget canvas on top of that. 22 | 23 | But in the case of negative elevation, we cannot just draw the widget's canvas on top 24 | of our shadows. Instead we dont add any canvas instructions for the widget and just 25 | create a outline of the widget in pillow. This outline is then used as a third texture, 26 | Below which we display our dark and light shadows. In this case the shadows are generated 27 | from outlines of the widgets rather than from a filled shape(as in the case of positive 28 | elevation.) 29 | 30 | This means as of now negtive elevation widgets will require a border. This behaviour 31 | will be replaced in the future by a class that can render the shadows such that a border 32 | is not needed(https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/03/LDgH6Eug.png?fit=1024%2C368&ssl=1). 33 | 34 | The current implementation will then be moved to its own class. 35 | """ 36 | 37 | blank_texture = ObjectProperty(None) 38 | """ 39 | A blank texture that is used if the elevation is set to 0 40 | """ 41 | 42 | dark_shadow = ObjectProperty(None) 43 | """ 44 | Object that holds the texture for the dark shadow 45 | """ 46 | 47 | dark_shadow_pos = ListProperty([0, 0]) 48 | """ 49 | A List containing the position of the dark shadow 50 | """ 51 | 52 | dark_shadow_size = ListProperty([0, 0]) 53 | """ 54 | List that contains the size of the dark shadow 55 | """ 56 | 57 | light_shadow = ObjectProperty(None) 58 | """ 59 | Object that holds the texture of the light shadow 60 | """ 61 | 62 | light_shadow_pos = ListProperty([0, 0]) 63 | """ 64 | A List containing the position of the light shadow 65 | """ 66 | light_shadow_size = ListProperty([0, 0]) 67 | """ 68 | List that contains the size of the light shadow 69 | """ 70 | 71 | border_texture = ObjectProperty(None) 72 | """ 73 | Object that holds the texture of the widget;s outline. This only has a texture 74 | if the elevation is set to negative. 75 | """ 76 | 77 | border_width = NumericProperty(10) 78 | """ 79 | Width of the outline texture in pixels. It defaults to 10 pixels.This property 80 | is only used if the widget has negative elevation 81 | """ 82 | 83 | increment = NumericProperty(0) 84 | """ 85 | Value to be used for blurring the shadow and shifting to them the correct location. 86 | You can change this value but it may ruin the neumorphic aesthetic of a widget. 87 | """ 88 | elev = NumericProperty(0) 89 | """ 90 | Elevation of the widget. This can be any value from -5 to 5 including both -5 and 5. 91 | It is possible to calculate shadows for other elevations but the neumorphic effect breaks 92 | down at values higher than this. It is suggested to keep the elevation of a widget between 93 | 1 and 3 or between -4 to -2 for the best effect. 94 | """ 95 | 96 | pixel_depth = NumericProperty(0) 97 | """ 98 | Internal property that is used to calculate how far shadows are shifted. 99 | You can change this value but it may cause the neumorphic effect to be ruined 100 | """ 101 | 102 | def __init__(self, *args, **kwargs): 103 | super(NeuMorphRectangle, self).__init__(*args, **kwargs) 104 | self.blank_texture = Texture.create(size=self.size, colorfmt="rgba") 105 | self._create_shadow() 106 | 107 | def _create_shadow(self, *args): 108 | if self.pixel_depth == 0: 109 | self.dark_shadow = self.blank_texture 110 | self.light_shadow = self.blank_texture 111 | return 112 | if self.elev > 0: 113 | self.increment = self.pixel_depth / 2.5 114 | # Create dark_shadow 115 | self.dark_shadow = self._outer_shadow_gen( 116 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 117 | ) 118 | self.dark_shadow_size = ( 119 | self.width + self.pixel_depth, 120 | self.height + self.pixel_depth, 121 | ) 122 | self.dark_shadow_pos = ( 123 | self.x - self.increment, 124 | self.y - self.pixel_depth / 2 - self.increment, 125 | ) 126 | # Create light shadow 127 | self.light_shadow = self._outer_shadow_gen( 128 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 129 | ) 130 | self.light_shadow_size = ( 131 | self.width + self.pixel_depth, 132 | self.height + self.pixel_depth, 133 | ) 134 | self.light_shadow_pos = ( 135 | self.x - self.pixel_depth / 2 - self.increment, 136 | self.y - self.increment, 137 | ) 138 | else: 139 | self.increment = self.pixel_depth / 2.5 140 | # Create widget outline 141 | self.border_texture = self._widget_outline( 142 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.comp_color) 143 | ) 144 | # Create dark_shadow 145 | self.dark_shadow = self._inner_shadow_gen( 146 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 147 | ) 148 | self.dark_shadow_size = ( 149 | self.width + self.pixel_depth, 150 | self.height + self.pixel_depth, 151 | ) 152 | self.dark_shadow_pos = ( 153 | self.x - self.pixel_depth / 2 + self.increment / 2, 154 | self.y - self.pixel_depth / 2 - self.increment / 2, 155 | ) 156 | # Create light shadow 157 | self.light_shadow = self._inner_shadow_gen( 158 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 159 | ) 160 | self.light_shadow_size = ( 161 | self.width + self.pixel_depth, 162 | self.height + self.pixel_depth, 163 | ) 164 | self.light_shadow_pos = ( 165 | self.x - self.pixel_depth / 2 - self.increment / 2, 166 | self.y - self.pixel_depth / 2 + self.increment / 2, 167 | ) 168 | 169 | def _outer_shadow_gen(self, size_x, size_y, pixel_depth, color): 170 | # Create blank image 171 | blank_x_size = int(size_x + pixel_depth) 172 | blank_y_size = int(size_y + pixel_depth) 173 | shadow = Image.new( 174 | "RGBA", 175 | (blank_x_size, blank_y_size), 176 | color=(tuple(dec_2_rgb(self.theme_manager._bg_color_noalp))), 177 | ) 178 | # Convert to drawable image 179 | blank_draw = ImageDraw.Draw(shadow) 180 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 181 | x1, y1 = x0 + size_x, y0 + size_y 182 | blank_draw.rectangle( 183 | [(x0, y0), (x1, y1)], 184 | fill=tuple(color), 185 | ) 186 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 187 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment)) 188 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 189 | return texture 190 | 191 | def _inner_shadow_gen(self, size_x, size_y, pixel_depth, color): 192 | # Create blank Image 193 | blank_x_size = int(size_x + pixel_depth) 194 | blank_y_size = int(size_y + pixel_depth) 195 | shadow = Image.new( 196 | "RGBA", 197 | (blank_x_size, blank_y_size), 198 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 199 | ) 200 | # Conver to drawable 201 | blank_draw = ImageDraw.Draw(shadow) 202 | # Calculate size for rectangle 203 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 204 | x1, y1 = size_x + self.increment, size_y + self.increment 205 | blank_draw.rectangle( 206 | [(x0, y0), (x1, y1)], 207 | outline=tuple(color), 208 | width=(int(self.increment)), 209 | ) 210 | # add filter and blit to texture 211 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment / 2)) 212 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 213 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 214 | return texture 215 | 216 | # Used to generate outline of widget 217 | def _widget_outline(self, size_x, size_y, pixel_depth, color): 218 | # Create blank Image 219 | outline = Image.new( 220 | "RGBA", 221 | (size_x, size_y), 222 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 223 | ) 224 | blank_draw = ImageDraw.Draw(outline) 225 | x0, y0 = 0, 0 226 | x1, y1 = x0 + size_x, y0 + size_y 227 | blank_draw.rectangle( 228 | [(x0, y0), (x1, y1)], 229 | outline=tuple(color), 230 | width=self.border_width, 231 | ) 232 | texture = Texture.create(size=(size_x, size_y), colorfmt="rgba") 233 | texture.blit_buffer(outline.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 234 | return texture 235 | 236 | def _update_pos(self, *args): 237 | if self.elev and self.elev > 0: 238 | self.dark_shadow_pos = ( 239 | self.x - self.increment, 240 | self.y - self.pixel_depth / 2 - self.increment, 241 | ) 242 | self.light_shadow_pos = ( 243 | self.x - self.pixel_depth / 2 - self.increment, 244 | self.y - self.increment, 245 | ) 246 | else: 247 | self.dark_shadow_pos = ( 248 | self.x - self.pixel_depth / 2 + self.increment / 2, 249 | self.y - self.pixel_depth / 2 - self.increment / 2, 250 | ) 251 | self.light_shadow_pos = ( 252 | self.x - self.pixel_depth / 2 - self.increment / 2, 253 | self.y - self.pixel_depth / 2 + self.increment / 2, 254 | ) 255 | 256 | def on_size(self, *args, **kwargs): 257 | self._create_shadow() 258 | 259 | def on_pos(self, *args, **kwargs): 260 | self._update_pos() 261 | 262 | def on_elev(self, instance, value): 263 | if abs(value) > 5: 264 | raise ValueError("Elevation must be between 5 and -5(inclusive)") 265 | self.pixel_depth = abs(value * 10) 266 | self._create_shadow() 267 | 268 | 269 | class NeuMorphRoundedRectangle: 270 | """ 271 | Creates rounded recatangular shadows 272 | 273 | 274 | This class is used to create the neumorphic shadows for widgets. Currently the 275 | method used is quite slow if there are large number of widgets on screen or if 276 | a widget is very big. This is especially apparent on android devices. 277 | 278 | This will eventually be fixed in the future, so for now you can create neumorphic ui 279 | on a pc. Just dont go too crazy unless you want a slideshow as your app. 280 | 281 | To create the neumorphic effect we basically create two shadows for each widget. 282 | One shadow is darker than the widget color and one is lighter. These two shadows are 283 | then positioned diagonally opposite of each of other to give the illusion of 3D. 284 | 285 | For positiive elevation we just blit these textures to the canvas.before and then 286 | draw our widget canvas on top of that. 287 | 288 | But in the case of negative elevation, we cannot just draw the widget's canvas on top 289 | of our shadows. Instead we dont add any canvas instructions for the widget and just 290 | create a outline of the widget in pillow. This outline is then used as a third texture, 291 | Below which we display our dark and light shadows. In this case the shadows are generated 292 | from outlines of the widgets rather than from a filled shape(as in the case of positive 293 | elevation.) 294 | 295 | This means as of now negtive elevation widgets will require a border. This behaviour 296 | will be replaced in the future by a class that can render the shadows such that a border 297 | is not needed(https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/03/LDgH6Eug.png?fit=1024%2C368&ssl=1). 298 | 299 | The current implementation will then be moved to its own class. 300 | """ 301 | 302 | blank_texture = ObjectProperty(None) 303 | """ 304 | A blank texture that is used if the elevation is set to 0 305 | """ 306 | 307 | dark_shadow = ObjectProperty(None) 308 | """ 309 | Object that holds the texture for the dark shadow 310 | """ 311 | 312 | dark_shadow_pos = ListProperty([0, 0]) 313 | """ 314 | A List containing the position of the dark shadow 315 | """ 316 | 317 | dark_shadow_size = ListProperty([0, 0]) 318 | """ 319 | List that contains the size of the dark shadow 320 | """ 321 | 322 | light_shadow = ObjectProperty(None) 323 | """ 324 | Object that holds the texture of the light shadow 325 | """ 326 | 327 | light_shadow_pos = ListProperty([0, 0]) 328 | """ 329 | A List containing the position of the light shadow 330 | """ 331 | light_shadow_size = ListProperty([0, 0]) 332 | """ 333 | List that contains the size of the light shadow 334 | """ 335 | 336 | border_texture = ObjectProperty(None) 337 | """ 338 | Object that holds the texture of the widget;s outline. This only has a texture 339 | if the elevation is set to negative. 340 | """ 341 | 342 | border_width = NumericProperty(10) 343 | """ 344 | Width of the outline texture in pixels. It defaults to 10 pixels.This property 345 | is only used if the widget has negative elevation 346 | """ 347 | 348 | increment = NumericProperty(0) 349 | """ 350 | Value to be used for blurring the shadow and shifting to them the correct location. 351 | You can change this value but it may ruin the neumorphic aesthetic of a widget. 352 | """ 353 | elev = NumericProperty(None) 354 | """ 355 | Elevation of the widget. This can be any value from -5 to 5 including both -5 and 5. 356 | It is possible to calculate shadows for other elevations but the neumorphic effect breaks 357 | down at values higher than this. It is suggested to keep the elevation of a widget between 358 | 1 and 3 or between -4 to -2 for the best effect. 359 | """ 360 | 361 | pixel_depth = NumericProperty(0) 362 | """ 363 | Internal property that is used to calculate how far shadows are shifted. 364 | You can change this value but it may cause the neumorphic effect to be ruined 365 | """ 366 | 367 | def __init__(self, *args, **kwargs): 368 | super(NeuMorphRoundedRectangle, self).__init__(*args, **kwargs) 369 | self.blank_texture = Texture.create(size=self.size, colorfmt="rgba") 370 | self._create_shadow() 371 | 372 | def _create_shadow(self, *args): 373 | if self.pixel_depth == 0: 374 | self.dark_shadow = self.blank_texture 375 | self.light_shadow = self.blank_texture 376 | return 377 | if self.elev > 0: 378 | self.increment = self.pixel_depth / 2.5 379 | # Create dark_shadow 380 | self.dark_shadow = self._outer_shadow_gen( 381 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 382 | ) 383 | self.dark_shadow_size = ( 384 | self.width + self.pixel_depth, 385 | self.height + self.pixel_depth, 386 | ) 387 | self.dark_shadow_pos = ( 388 | self.x - self.increment, 389 | self.y - self.pixel_depth / 2 - self.increment, 390 | ) 391 | # Create light shadow 392 | self.light_shadow = self._outer_shadow_gen( 393 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 394 | ) 395 | self.light_shadow_size = ( 396 | self.width + self.pixel_depth, 397 | self.height + self.pixel_depth, 398 | ) 399 | self.light_shadow_pos = ( 400 | self.x - self.pixel_depth / 2 - self.increment, 401 | self.y - self.increment, 402 | ) 403 | else: 404 | self.increment = self.pixel_depth / 2.5 405 | # Create widget outline 406 | self.border_texture = self._widget_outline( 407 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.comp_color) 408 | ) 409 | # Create dark_shadow 410 | self.dark_shadow = self._inner_shadow_gen( 411 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 412 | ) 413 | self.dark_shadow_size = ( 414 | self.width + self.pixel_depth, 415 | self.height + self.pixel_depth, 416 | ) 417 | self.dark_shadow_pos = ( 418 | self.x - self.pixel_depth / 2 + self.increment / 2, 419 | self.y - self.pixel_depth / 2 - self.increment / 2, 420 | ) 421 | # Create light shadow 422 | self.light_shadow = self._inner_shadow_gen( 423 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 424 | ) 425 | self.light_shadow_size = ( 426 | self.width + self.pixel_depth, 427 | self.height + self.pixel_depth, 428 | ) 429 | self.light_shadow_pos = ( 430 | self.x - self.pixel_depth / 2 - self.increment / 2, 431 | self.y - self.pixel_depth / 2 + self.increment / 2, 432 | ) 433 | 434 | def _outer_shadow_gen(self, size_x, size_y, pixel_depth, color): 435 | # Create blank image 436 | blank_x_size = int(size_x + pixel_depth) 437 | blank_y_size = int(size_y + pixel_depth) 438 | shadow = Image.new( 439 | "RGBA", 440 | (blank_x_size, blank_y_size), 441 | color=(tuple(dec_2_rgb(self.theme_manager._bg_color_noalp))), 442 | ) 443 | # Convert to drawable image 444 | blank_draw = ImageDraw.Draw(shadow) 445 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 446 | x1, y1 = x0 + size_x, y0 + size_y 447 | blank_draw.rounded_rectangle( 448 | [(x0, y0), (x1, y1)], 449 | radius=self.radius, 450 | fill=tuple(color), 451 | ) 452 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 453 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment)) 454 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 455 | return texture 456 | 457 | def _inner_shadow_gen(self, size_x, size_y, pixel_depth, color): 458 | # Create blank Image 459 | blank_x_size = int(size_x + pixel_depth) 460 | blank_y_size = int(size_y + pixel_depth) 461 | shadow = Image.new( 462 | "RGBA", 463 | (blank_x_size, blank_y_size), 464 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 465 | ) 466 | # Conver to drawable 467 | blank_draw = ImageDraw.Draw(shadow) 468 | # Calculate size for rectangle 469 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 470 | x1, y1 = size_x + self.increment, size_y + self.increment 471 | blank_draw.rounded_rectangle( 472 | [(x0, y0), (x1, y1)], 473 | radius=self.radius, 474 | outline=tuple(color), 475 | width=(int(self.increment)), 476 | ) 477 | # add filter and blit to texture 478 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment / 2)) 479 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 480 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 481 | return texture 482 | 483 | # Used to generate outline of widget 484 | def _widget_outline(self, size_x, size_y, pixel_depth, color): 485 | # Create blank Image 486 | outline = Image.new( 487 | "RGBA", 488 | (int(size_x), int(size_y)), 489 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 490 | ) 491 | blank_draw = ImageDraw.Draw(outline) 492 | x0, y0 = 0, 0 493 | x1, y1 = x0 + size_x, y0 + size_y 494 | blank_draw.rounded_rectangle( 495 | [(x0, y0), (x1, y1)], 496 | radius=self.radius, 497 | outline=tuple(color), 498 | width=self.border_width, 499 | ) 500 | texture = Texture.create(size=(size_x, size_y), colorfmt="rgba") 501 | texture.blit_buffer(outline.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 502 | return texture 503 | 504 | def _update_pos(self, *args): 505 | if self.elev and self.elev > 0: 506 | self.dark_shadow_pos = ( 507 | self.x - self.increment, 508 | self.y - self.pixel_depth / 2 - self.increment, 509 | ) 510 | self.light_shadow_pos = ( 511 | self.x - self.pixel_depth / 2 - self.increment, 512 | self.y - self.increment, 513 | ) 514 | else: 515 | self.dark_shadow_pos = ( 516 | self.x - self.pixel_depth / 2 + self.increment / 2, 517 | self.y - self.pixel_depth / 2 - self.increment / 2, 518 | ) 519 | self.light_shadow_pos = ( 520 | self.x - self.pixel_depth / 2 - self.increment / 2, 521 | self.y - self.pixel_depth / 2 + self.increment / 2, 522 | ) 523 | 524 | def on_size(self, *args, **kwargs): 525 | self._create_shadow() 526 | 527 | def on_pos(self, *args, **kwargs): 528 | self._update_pos() 529 | 530 | def on_elev(self, instance, value): 531 | if abs(value) > 5: 532 | raise ValueError("Elevation must be between 5 and -5(inclusive)") 533 | self.pixel_depth = abs(value * 10) 534 | self._create_shadow() 535 | 536 | def on_radius(self, instance, value): 537 | self._create_shadow() 538 | 539 | 540 | class NeuMorphCircular: 541 | """ 542 | Creates Circular shadows 543 | 544 | 545 | This class is used to create the neumorphic shadows for widgets. Currently the 546 | method used is quite slow if there are large number of widgets on screen or if 547 | a widget is very big. This is especially apparent on android devices. 548 | 549 | This will eventually be fixed in the future, so for now you can create neumorphic ui 550 | on a pc. Just dont go too crazy unless you want a slideshow as your app. 551 | 552 | To create the neumorphic effect we basically create two shadows for each widget. 553 | One shadow is darker than the widget color and one is lighter. These two shadows are 554 | then positioned diagonally opposite of each of other to give the illusion of 3D. 555 | 556 | For positiive elevation we just blit these textures to the canvas.before and then 557 | draw our widget canvas on top of that. 558 | 559 | But in the case of negative elevation, we cannot just draw the widget's canvas on top 560 | of our shadows. Instead we dont add any canvas instructions for the widget and just 561 | create a outline of the widget in pillow. This outline is then used as a third texture, 562 | Below which we display our dark and light shadows. In this case the shadows are generated 563 | from outlines of the widgets rather than from a filled shape(as in the case of positive 564 | elevation.) 565 | 566 | This means as of now negtive elevation widgets will require a border. This behaviour 567 | will be replaced in the future by a class that can render the shadows such that a border 568 | is not needed(https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/03/LDgH6Eug.png?fit=1024%2C368&ssl=1). 569 | 570 | The current implementation will then be moved to its own class. 571 | """ 572 | 573 | blank_texture = ObjectProperty(None) 574 | """ 575 | A blank texture that is used if the elevation is set to 0 576 | """ 577 | 578 | dark_shadow = ObjectProperty(None) 579 | """ 580 | Object that holds the texture for the dark shadow 581 | """ 582 | 583 | dark_shadow_pos = ListProperty([0, 0]) 584 | """ 585 | A List containing the position of the dark shadow 586 | """ 587 | 588 | dark_shadow_size = ListProperty([0, 0]) 589 | """ 590 | List that contains the size of the dark shadow 591 | """ 592 | 593 | light_shadow = ObjectProperty(None) 594 | """ 595 | Object that holds the texture of the light shadow 596 | """ 597 | 598 | light_shadow_pos = ListProperty([0, 0]) 599 | """ 600 | A List containing the position of the light shadow 601 | """ 602 | light_shadow_size = ListProperty([0, 0]) 603 | """ 604 | List that contains the size of the light shadow 605 | """ 606 | 607 | border_texture = ObjectProperty(None) 608 | """ 609 | Object that holds the texture of the widget;s outline. This only has a texture 610 | if the elevation is set to negative. 611 | """ 612 | 613 | border_width = NumericProperty(10) 614 | """ 615 | Width of the outline texture in pixels. It defaults to 10 pixels.This property 616 | is only used if the widget has negative elevation 617 | """ 618 | 619 | increment = NumericProperty(0) 620 | """ 621 | Value to be used for blurring the shadow and shifting to them the correct location. 622 | You can change this value but it may ruin the neumorphic aesthetic of a widget. 623 | """ 624 | elev = NumericProperty(None) 625 | """ 626 | Elevation of the widget. This can be any value from -5 to 5 including both -5 and 5. 627 | It is possible to calculate shadows for other elevations but the neumorphic effect breaks 628 | down at values higher than this. It is suggested to keep the elevation of a widget between 629 | 1 and 3 or between -4 to -2 for the best effect. 630 | """ 631 | 632 | pixel_depth = NumericProperty(0) 633 | """ 634 | Internal property that is used to calculate how far shadows are shifted. 635 | You can change this value but it may cause the neumorphic effect to be ruined 636 | """ 637 | 638 | def __init__(self, *args, **kwargs): 639 | super(NeuMorphCircular, self).__init__(*args, **kwargs) 640 | self.blank_texture = Texture.create(size=self.size, colorfmt="rgba") 641 | self._create_shadow() 642 | 643 | def _create_shadow(self, *args): 644 | if self.pixel_depth == 0: 645 | self.dark_shadow = self.blank_texture 646 | self.light_shadow = self.blank_texture 647 | return 648 | if self.elev > 0: 649 | self.increment = self.pixel_depth / 2.5 650 | # Create dark_shadow 651 | self.dark_shadow = self._outer_shadow_gen( 652 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 653 | ) 654 | self.dark_shadow_size = ( 655 | self.width + self.pixel_depth, 656 | self.height + self.pixel_depth, 657 | ) 658 | self.dark_shadow_pos = ( 659 | self.x - self.increment, 660 | self.y - self.pixel_depth / 2 - self.increment, 661 | ) 662 | # Create light shadow 663 | self.light_shadow = self._outer_shadow_gen( 664 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 665 | ) 666 | self.light_shadow_size = ( 667 | self.width + self.pixel_depth, 668 | self.height + self.pixel_depth, 669 | ) 670 | self.light_shadow_pos = ( 671 | self.x - self.pixel_depth / 2 - self.increment, 672 | self.y - self.increment, 673 | ) 674 | else: 675 | self.increment = self.pixel_depth / 2.5 676 | # Create widget outline 677 | self.border_texture = self._widget_outline( 678 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.comp_color) 679 | ) 680 | # Create dark_shadow 681 | self.dark_shadow = self._inner_shadow_gen( 682 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.dark_color) 683 | ) 684 | self.dark_shadow_size = ( 685 | self.width + self.pixel_depth, 686 | self.height + self.pixel_depth, 687 | ) 688 | self.dark_shadow_pos = ( 689 | self.x - self.pixel_depth / 2 + self.increment / 2, 690 | self.y - self.pixel_depth / 2 - self.increment / 2, 691 | ) 692 | # Create light shadow 693 | self.light_shadow = self._inner_shadow_gen( 694 | self.width, self.height, self.pixel_depth, dec_2_rgb(self.light_color) 695 | ) 696 | self.light_shadow_size = ( 697 | self.width + self.pixel_depth, 698 | self.height + self.pixel_depth, 699 | ) 700 | self.light_shadow_pos = ( 701 | self.x - self.pixel_depth / 2 - self.increment / 2, 702 | self.y - self.pixel_depth / 2 + self.increment / 2, 703 | ) 704 | 705 | def _outer_shadow_gen(self, size_x, size_y, pixel_depth, color): 706 | # Create blank image 707 | blank_x_size = int(size_x + pixel_depth) 708 | blank_y_size = int(size_y + pixel_depth) 709 | shadow = Image.new( 710 | "RGBA", 711 | (blank_x_size, blank_y_size), 712 | color=(tuple(dec_2_rgb(self.theme_manager._bg_color_noalp))), 713 | ) 714 | # Convert to drawable image 715 | blank_draw = ImageDraw.Draw(shadow) 716 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 717 | x1, y1 = x0 + size_x, y0 + size_y 718 | blank_draw.ellipse( 719 | [(x0, y0), (x1, y1)], 720 | fill=tuple(color), 721 | ) 722 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 723 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment)) 724 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 725 | return texture 726 | 727 | def _inner_shadow_gen(self, size_x, size_y, pixel_depth, color): 728 | # Create blank Image 729 | blank_x_size = int(size_x + pixel_depth) 730 | blank_y_size = int(size_y + pixel_depth) 731 | shadow = Image.new( 732 | "RGBA", 733 | (blank_x_size, blank_y_size), 734 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 735 | ) 736 | # Conver to drawable 737 | blank_draw = ImageDraw.Draw(shadow) 738 | # Calculate size for rectangle 739 | x0, y0 = (blank_x_size - size_x) / 2.0, (blank_y_size - size_y) / 2.0 740 | x1, y1 = size_x + self.increment, size_y + self.increment 741 | blank_draw.ellipse( 742 | [(x0, y0), (x1, y1)], 743 | outline=tuple(color), 744 | width=(int(self.increment)), 745 | ) 746 | # add filter and blit to texture 747 | shadow = shadow.filter(ImageFilter.GaussianBlur(self.increment / 2)) 748 | texture = Texture.create(size=(blank_x_size, blank_y_size), colorfmt="rgba") 749 | texture.blit_buffer(shadow.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 750 | return texture 751 | 752 | # Used to generate outline of widget 753 | def _widget_outline(self, size_x, size_y, pixel_depth, color): 754 | # Create blank Image 755 | outline = Image.new( 756 | "RGBA", 757 | (size_x, size_y), 758 | color=tuple(dec_2_rgb(self.theme_manager._bg_color_noalp)), 759 | ) 760 | blank_draw = ImageDraw.Draw(outline) 761 | x0, y0 = 0, 0 762 | x1, y1 = x0 + size_x, y0 + size_y 763 | blank_draw.ellipse( 764 | [(x0, y0), (x1, y1)], 765 | outline=tuple(color), 766 | width=self.border_width, 767 | ) 768 | texture = Texture.create(size=(size_x, size_y), colorfmt="rgba") 769 | texture.blit_buffer(outline.tobytes(), colorfmt="rgba", bufferfmt="ubyte") 770 | return texture 771 | 772 | def _update_pos(self, *args): 773 | if self.elev and self.elev > 0: 774 | self.dark_shadow_pos = ( 775 | self.x - self.increment, 776 | self.y - self.pixel_depth / 2 - self.increment, 777 | ) 778 | self.light_shadow_pos = ( 779 | self.x - self.pixel_depth / 2 - self.increment, 780 | self.y - self.increment, 781 | ) 782 | else: 783 | self.dark_shadow_pos = ( 784 | self.x - self.pixel_depth / 2 + self.increment / 2, 785 | self.y - self.pixel_depth / 2 - self.increment / 2, 786 | ) 787 | self.light_shadow_pos = ( 788 | self.x - self.pixel_depth / 2 - self.increment / 2, 789 | self.y - self.pixel_depth / 2 + self.increment / 2, 790 | ) 791 | 792 | def on_size(self, *args, **kwargs): 793 | self._create_shadow() 794 | 795 | def on_pos(self, *args, **kwargs): 796 | self._update_pos() 797 | 798 | def on_elev(self, instance, value): 799 | if abs(value) > 5: 800 | raise ValueError("Elevation must be between 5 and -5(inclusive)") 801 | self.pixel_depth = abs(value * 10) 802 | self._create_shadow() 803 | -------------------------------------------------------------------------------- /neukivy/uix/behaviors/themeablebehavior.py: -------------------------------------------------------------------------------- 1 | from kivy.app import App 2 | from kivy.clock import Clock 3 | from kivy.event import EventDispatcher 4 | from kivy.properties import ObjectProperty 5 | 6 | 7 | class ThemeableBehavior(EventDispatcher): 8 | 9 | theme_manager = ObjectProperty() 10 | """ 11 | Theme Manager object that contains all the default color values of the app instance. 12 | If a widget does not define its own color values it will automatically use the app defaults. 13 | It is suggested to not customize the color for each component as this will ruin the neumorphic style. 14 | """ 15 | 16 | def __init__(self, **kwargs): 17 | self.theme_manager = App.get_running_app().theme_manager 18 | self.color_setter() 19 | super().__init__(**kwargs) 20 | Clock.schedule_once(self.color_setter, -1) 21 | 22 | def color_setter(self, *args): 23 | """ 24 | Sets the color properties of a widget to the app defaults if no color properties 25 | for that widget are provided. 26 | """ 27 | if hasattr(self, "comp_color") and self.comp_color == [0, 0, 0, 0]: 28 | self.comp_color = self.theme_manager._bg_color_alp 29 | if hasattr(self, "dark_color") and self.dark_color == [0, 0, 0, 0]: 30 | self.dark_color = self.theme_manager.dark_color 31 | if hasattr(self, "light_color") and self.light_color == [0, 0, 0, 0]: 32 | self.light_color = self.theme_manager.light_color 33 | if hasattr(self, "text_color") and self.text_color == [0, 0, 0, 0]: 34 | self.text_color = self.theme_manager.text_color 35 | -------------------------------------------------------------------------------- /neukivy/uix/button.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.lang import Builder 3 | from kivy.properties import ( 4 | BooleanProperty, 5 | ColorProperty, 6 | ListProperty, 7 | NumericProperty, 8 | OptionProperty, 9 | StringProperty, 10 | ) 11 | from kivy.uix.anchorlayout import AnchorLayout 12 | from kivy.uix.boxlayout import BoxLayout 13 | from kivy.uix.label import Label 14 | from neukivy.icon_definitions import icons_dict 15 | from neukivy.uix.behaviors.neubuttonbehavior import NeuButtonBehavior 16 | from neukivy.uix.behaviors.neumorph import ( 17 | NeuMorphCircular, 18 | NeuMorphRectangle, 19 | NeuMorphRoundedRectangle, 20 | ) 21 | from neukivy.uix.behaviors.themeablebehavior import ThemeableBehavior 22 | 23 | Builder.load_string( 24 | """ 25 | 26 | : 27 | canvas.before: 28 | Clear 29 | Color: 30 | Rectangle: 31 | size:self.light_shadow_size 32 | pos:self.light_shadow_pos 33 | texture:self.light_shadow 34 | Color: 35 | rgba:1,1,1,1 36 | Rectangle: 37 | size:self.dark_shadow_size 38 | pos:self.dark_shadow_pos 39 | texture:self.dark_shadow 40 | size:100,100 41 | size_hint:None,None 42 | anchor_x:"center" 43 | anchor_y:"center" 44 | Label: 45 | id:label 46 | text:root.text 47 | size:self.texture_size 48 | size_hint:None,None 49 | font_size:root.font_size 50 | italic:root.italic 51 | color:root.text_color 52 | markup: True 53 | disabled: root.disabled 54 | font_name: root.font_name if root.font_name else 'NunitoSemiBold' 55 | 56 | : 57 | canvas.before: 58 | Clear 59 | Color: 60 | Rectangle: 61 | size:self.light_shadow_size 62 | pos:self.light_shadow_pos 63 | texture:self.light_shadow 64 | Color: 65 | rgba:1,1,1,1 66 | Rectangle: 67 | size:self.dark_shadow_size 68 | pos:self.dark_shadow_pos 69 | texture:self.dark_shadow 70 | size_hint:None,None 71 | width:self.minimum_width 72 | height:self.minimum_height 73 | Label: 74 | id:label 75 | text:root.text 76 | size:self.texture_size 77 | size_hint:None,None 78 | font_size:root.font_size 79 | italic:root.italic 80 | color:root.text_color 81 | markup: True 82 | disabled: root.disabled 83 | font_name: root.font_name if root.font_name else 'NunitoSemiBold' 84 | pos_hint:{'center_x':.5,'center_y':.5} 85 | Label: 86 | id:icon 87 | text:root._icon_text 88 | size:self.texture_size 89 | size_hint:None,None 90 | font_size:root.icon_font_size 91 | italic:root.italic 92 | color:root.text_color if root.icon_color == [0,0,0,0] else root.icon_color 93 | markup: True 94 | disabled: root.disabled 95 | font_name: root.icon_font_name if root.font_name else 'Icons' 96 | pos_hint:{'center_x':.5,'center_y':.5} 97 | 98 | : 99 | canvas.before: 100 | Color: 101 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 102 | Rectangle: 103 | size:self.size 104 | pos:self.pos 105 | texture:self.border_texture if self.elev and self.elev < 0 else None 106 | Color: 107 | size:100,100 108 | size_hint:None,None 109 | 110 | : 111 | canvas.before: 112 | Color: 113 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 114 | RoundedRectangle: 115 | size:self.size 116 | pos:self.pos 117 | radius:self.radius,self.radius,self.radius,self.radius 118 | texture:self.border_texture if self.elev and self.elev < 0 else None 119 | Color: 120 | 121 | : 122 | canvas.before: 123 | Color: 124 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 125 | Ellipse: 126 | size:self.radius,self.radius 127 | pos:self.pos 128 | texture:self.border_texture if self.elev and self.elev < 0 else None 129 | Color: 130 | size:self.radius,self.radius 131 | 132 | : 133 | canvas.before: 134 | Color: 135 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 136 | Rectangle: 137 | size:self.size 138 | pos:self.pos 139 | texture:self.border_texture if self.elev and self.elev < 0 else None 140 | Color: 141 | 142 | : 143 | canvas.before: 144 | Color: 145 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 146 | RoundedRectangle: 147 | size:self.size 148 | pos:self.pos 149 | radius:self.radius,self.radius,self.radius,self.radius 150 | texture:self.border_texture if self.elev and self.elev < 0 else None 151 | Color: 152 | 153 | : 154 | canvas.before: 155 | Color: 156 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 157 | Ellipse: 158 | size:self.radius,self.radius 159 | pos:self.pos 160 | texture:self.border_texture if self.elev and self.elev < 0 else None 161 | Color: 162 | size:self.radius,self.radius 163 | 164 | : 165 | canvas.before: 166 | Color: 167 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 168 | Rectangle: 169 | size:self.size 170 | pos:self.pos 171 | texture:self.border_texture if self.elev and self.elev < 0 else None 172 | Color: 173 | 174 | : 175 | canvas.before: 176 | Color: 177 | rgba:(1,1,1,1) if self.elev and self.elev < 0 else self.comp_color 178 | RoundedRectangle: 179 | size:self.size 180 | pos:self.pos 181 | radius:self.radius,self.radius,self.radius,self.radius 182 | texture:self.border_texture if self.elev and self.elev < 0 else None 183 | Color: 184 | 185 | 186 | """ 187 | ) 188 | 189 | 190 | class NeuBaseButton(NeuButtonBehavior, AnchorLayout, ThemeableBehavior): 191 | 192 | text = StringProperty() 193 | """ 194 | Button text 195 | 196 | attr:`text` is an :class:`~kivy.properties.StringProperty` 197 | and defaults to `' '`. 198 | """ 199 | 200 | font_size = NumericProperty("14sp") 201 | """ 202 | Size of font used 203 | 204 | attr:`font_size` is an :class:`~kivy.properties.NumericProperty` 205 | and defaults to `"14sp"`. 206 | """ 207 | 208 | disabled = BooleanProperty(False) 209 | """ 210 | Whether the button is disabled or not. When a button is disabled its text color 211 | is greyed out and it is not longer clickable 212 | 213 | attr:`disabled` is an :class:`~kivy.properties.BooleanProperty` 214 | and defaults to `False`. 215 | """ 216 | 217 | font_name = StringProperty(default="NunitoSemiBold") 218 | """ 219 | Name of the face to be used 220 | 221 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 222 | and defaults to `'NunitoSemiBold'`. 223 | """ 224 | 225 | text_color = ColorProperty([0, 0, 0, 0]) 226 | """ 227 | Text color 228 | 229 | attr:`text_color` is an :class:`~kivy.properties.ColorProperty` 230 | and defaults to `[0,0,0,0]`. 231 | """ 232 | 233 | italic = BooleanProperty(False) 234 | """ 235 | If set tot true the text will be rendered with its italic font type. WIll only 236 | work if the given font name has an itallic type. 237 | 238 | attr:`italic` is an :class:`~kivy.properties.BooleanProperty` 239 | and defaults to `False`. 240 | """ 241 | 242 | 243 | class NeuIconTextBaseButton(NeuButtonBehavior, BoxLayout, ThemeableBehavior): 244 | 245 | text = StringProperty() 246 | """ 247 | Button text 248 | 249 | attr:`text` is an :class:`~kivy.properties.StringProperty` 250 | and defaults to `' '`. 251 | """ 252 | 253 | font_size = NumericProperty("14sp") 254 | """ 255 | Size of font used 256 | 257 | attr:`font_size` is an :class:`~kivy.properties.NumericProperty` 258 | and defaults to `"14sp"`. 259 | """ 260 | 261 | icon_font_size = NumericProperty("14sp") 262 | """ 263 | Size of font used fpr the icon 264 | 265 | attr:`icon_font_size` is an :class:`~kivy.properties.NumericProperty` 266 | and defaults to `"14sp"`. 267 | """ 268 | 269 | disabled = BooleanProperty(False) 270 | """ 271 | Whether the button is disabled or not. When a button is disabled its text color 272 | is greyed out and it is not longer clickable 273 | 274 | attr:`disabled` is an :class:`~kivy.properties.BooleanProperty` 275 | and defaults to `False`. 276 | """ 277 | 278 | font_name = StringProperty(default="NunitoSemiBold") 279 | """ 280 | Name of the font face to be used 281 | 282 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 283 | and defaults to `'NunitoSemiBold'`. 284 | """ 285 | 286 | icon_font_name = StringProperty("Icons") 287 | """ 288 | Name of the font used for icon definitions. 289 | 290 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 291 | and defaults to `"Icons"`. 292 | """ 293 | 294 | text_color = ColorProperty([0, 0, 0, 0]) 295 | """ 296 | Text color 297 | 298 | attr:`text_color` is an :class:`~kivy.properties.ColorProperty` 299 | and defaults to `[0,0,0,0]`. 300 | """ 301 | 302 | icon_color = ColorProperty([0, 0, 0, 0]) 303 | """ 304 | Icon color 305 | 306 | attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` 307 | and defaults to `[0,0,0,0]`. 308 | """ 309 | 310 | italic = BooleanProperty(False) 311 | """ 312 | If set tot true the text will be rendered with its italic font type. WIll only 313 | work if the given font name has an itallic type. 314 | 315 | attr:`italic` is an :class:`~kivy.properties.BooleanProperty` 316 | and defaults to `False`. 317 | """ 318 | 319 | padding = ListProperty([10, 10, 10, 10]) 320 | """ 321 | Padding around the text and icon of the button 322 | 323 | attr:`padding` is an :class:`~kivy.properties.NumericProperty` 324 | and defaults to `[10, 10, 10, 10]`. 325 | """ 326 | 327 | spacing = NumericProperty("10dp") 328 | """ 329 | Spacing between the icon and button 330 | 331 | attr:`spacing` is an :class:`~kivy.properties.NumericProperty` 332 | and defaults to `"10dp"`. 333 | """ 334 | 335 | icon_pos = OptionProperty("right", options=["right", "left", "top", "bottom"]) 336 | 337 | def on_icon(self, *args): 338 | if self.icon_font_name == "Icons": 339 | try: 340 | self._icon_text = icons_dict[self.icon] 341 | except KeyError: 342 | raise KeyError("The icon '" + self.icon + "' does not exist") 343 | 344 | def on_icon_pos(self, *args): 345 | if self.icon_pos == "right": 346 | label = self.ids.label 347 | icon = self.ids.icon 348 | self.orientation = "horizontal" 349 | self.clear_widgets() 350 | self.add_widget(label) 351 | self.add_widget(icon) 352 | elif self.icon_pos == "left": 353 | label = self.ids.label 354 | icon = self.ids.icon 355 | self.orientation = "horizontal" 356 | self.clear_widgets() 357 | self.add_widget(icon) 358 | self.add_widget(label) 359 | elif self.icon_pos == "top": 360 | label = self.ids.label 361 | icon = self.ids.icon 362 | self.orientation = "vertical" 363 | self.clear_widgets() 364 | self.add_widget(icon) 365 | self.add_widget(label) 366 | else: 367 | label = self.ids.label 368 | icon = self.ids.icon 369 | self.orientation = "vertical" 370 | self.clear_widgets() 371 | self.add_widget(label) 372 | self.add_widget(icon) 373 | 374 | 375 | class NeuButton(NeuBaseButton, NeuMorphRectangle): 376 | 377 | comp_color = ListProperty([0, 0, 0, 0]) 378 | 379 | dark_color = ListProperty([0, 0, 0, 0]) 380 | 381 | light_color = ListProperty([0, 0, 0, 0]) 382 | 383 | 384 | class NeuRoundedButton(NeuBaseButton, NeuMorphRoundedRectangle): 385 | 386 | comp_color = ListProperty([0, 0, 0, 0]) 387 | 388 | dark_color = ListProperty([0, 0, 0, 0]) 389 | 390 | light_color = ListProperty([0, 0, 0, 0]) 391 | 392 | radius = NumericProperty(20) 393 | """ 394 | Radius of the corners 395 | """ 396 | 397 | 398 | class NeuCircularButton(NeuBaseButton, NeuMorphCircular): 399 | 400 | comp_color = ListProperty([0, 0, 0, 0]) 401 | 402 | dark_color = ListProperty([0, 0, 0, 0]) 403 | 404 | light_color = ListProperty([0, 0, 0, 0]) 405 | 406 | radius = NumericProperty(100) 407 | """ 408 | Radius of the button 409 | """ 410 | 411 | def on_size(self, *args): 412 | self.size = self.radius, self.radius 413 | 414 | 415 | class NeuIconButton(NeuBaseButton, NeuMorphRectangle): 416 | 417 | comp_color = ListProperty([0, 0, 0, 0]) 418 | 419 | dark_color = ListProperty([0, 0, 0, 0]) 420 | 421 | light_color = ListProperty([0, 0, 0, 0]) 422 | 423 | font_name = StringProperty("Icons") 424 | """ 425 | Name of the font used for icon definitions. 426 | 427 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 428 | and defaults to `"Icons"`. 429 | """ 430 | 431 | icon = StringProperty("android") 432 | """ 433 | Icon used in the button 434 | 435 | attr:`icon` is an :class:`~kivy.properties.StringProperty` 436 | and defaults to `"android"`. 437 | """ 438 | 439 | def on_icon(self, *args): 440 | if self.font_name == "Icons": 441 | try: 442 | self.text = icons_dict[self.icon] 443 | except KeyError: 444 | raise KeyError("The icon '" + self.icon + "' does not exist") 445 | 446 | 447 | class NeuRoundedIconButton(NeuBaseButton, NeuMorphRoundedRectangle): 448 | 449 | comp_color = ListProperty([0, 0, 0, 0]) 450 | 451 | dark_color = ListProperty([0, 0, 0, 0]) 452 | 453 | light_color = ListProperty([0, 0, 0, 0]) 454 | 455 | radius = NumericProperty(20) 456 | """ 457 | Radius of the corners 458 | """ 459 | 460 | font_name = StringProperty("Icons") 461 | """ 462 | Name of the font used for icon definitions. 463 | 464 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 465 | and defaults to `"Icons"`. 466 | """ 467 | 468 | icon = StringProperty("android") 469 | """ 470 | Icon used in the button 471 | 472 | attr:`icon` is an :class:`~kivy.properties.StringProperty` 473 | and defaults to `"android"`. 474 | """ 475 | 476 | def on_icon(self, *args): 477 | if self.font_name == "Icons": 478 | try: 479 | self.text = icons_dict[self.icon] 480 | except KeyError: 481 | raise KeyError("The icon '" + self.icon + "' does not exist") 482 | 483 | 484 | class NeuCircularIconButton(NeuBaseButton, NeuMorphCircular): 485 | 486 | comp_color = ListProperty([0, 0, 0, 0]) 487 | 488 | dark_color = ListProperty([0, 0, 0, 0]) 489 | 490 | light_color = ListProperty([0, 0, 0, 0]) 491 | 492 | radius = NumericProperty(100) 493 | """ 494 | Radius of the button 495 | """ 496 | 497 | font_name = StringProperty("Icons") 498 | """ 499 | Name of the font used for icon definitions. 500 | 501 | attr:`font_name` is an :class:`~kivy.properties.StringProperty` 502 | and defaults to `"Icons"`. 503 | """ 504 | 505 | icon = StringProperty("android") 506 | """ 507 | Icon used in the button 508 | 509 | attr:`icon` is an :class:`~kivy.properties.StringProperty` 510 | and defaults to `"android"`. 511 | """ 512 | 513 | comp_color = ListProperty([0, 0, 0, 0]) 514 | 515 | dark_color = ListProperty([0, 0, 0, 0]) 516 | 517 | light_color = ListProperty([0, 0, 0, 0]) 518 | 519 | icon = StringProperty("android") 520 | 521 | text = StringProperty() 522 | 523 | def on_icon(self, *args): 524 | if self.font_name == "Icons": 525 | try: 526 | self.text = icons_dict[self.icon] 527 | except KeyError: 528 | raise KeyError("The icon '" + self.icon + "' does not exist") 529 | 530 | def on_size(self, *args): 531 | self.size = self.radius, self.radius 532 | 533 | 534 | class NeuIconTextButton(NeuIconTextBaseButton, NeuMorphRectangle): 535 | 536 | comp_color = ListProperty([0, 0, 0, 0]) 537 | 538 | dark_color = ListProperty([0, 0, 0, 0]) 539 | 540 | light_color = ListProperty([0, 0, 0, 0]) 541 | 542 | icon = StringProperty("android") 543 | """ 544 | Icon used in the button 545 | 546 | attr:`icon` is an :class:`~kivy.properties.StringProperty` 547 | and defaults to `"android"`. 548 | """ 549 | 550 | _icon_text = StringProperty() 551 | 552 | 553 | class NeuRoundedIconTextButton(NeuIconTextBaseButton, NeuMorphRoundedRectangle): 554 | 555 | comp_color = ListProperty([0, 0, 0, 0]) 556 | 557 | dark_color = ListProperty([0, 0, 0, 0]) 558 | 559 | light_color = ListProperty([0, 0, 0, 0]) 560 | 561 | radius = NumericProperty(20) 562 | """ 563 | Radius of the corners 564 | """ 565 | 566 | icon = StringProperty("android") 567 | """ 568 | Icon used in the button 569 | 570 | attr:`icon` is an :class:`~kivy.properties.StringProperty` 571 | and defaults to `"android"`. 572 | """ 573 | 574 | _icon_text = StringProperty() 575 | -------------------------------------------------------------------------------- /neukivy/uix/card.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.lang import Builder 3 | from kivy.properties import ListProperty, NumericProperty 4 | from kivy.uix.boxlayout import BoxLayout 5 | from neukivy.uix.behaviors.neumorph import NeuMorphRoundedRectangle 6 | from neukivy.uix.behaviors.themeablebehavior import ThemeableBehavior 7 | 8 | Builder.load_string( 9 | """ 10 | : 11 | canvas.before: 12 | Clear 13 | Color: 14 | Rectangle: 15 | size:self.light_shadow_size 16 | pos:self.light_shadow_pos 17 | texture:self.light_shadow 18 | Color: 19 | rgba:1,1,1,1 20 | Rectangle: 21 | size:self.dark_shadow_size 22 | pos:self.dark_shadow_pos 23 | texture:self.dark_shadow 24 | canvas: 25 | Color: 26 | rgba:(1,1,1,1) if self.elevation and self.elevation < 0 else self.comp_color 27 | RoundedRectangle: 28 | size:self.size 29 | pos:self.pos 30 | radius:self.radius,self.radius,self.radius,self.radius 31 | texture:self.border_texture if self.elevation and self.elevation < 0 else None 32 | Color: 33 | 34 | 35 | 36 | """ 37 | ) 38 | 39 | 40 | class NeuCard(ThemeableBehavior, BoxLayout, NeuMorphRoundedRectangle): 41 | 42 | comp_color = ListProperty([0, 0, 0, 0]) 43 | 44 | dark_color = ListProperty([0, 0, 0, 0]) 45 | 46 | light_color = ListProperty([0, 0, 0, 0]) 47 | 48 | radius = NumericProperty("20dp") 49 | """ 50 | Radius of the edges of the card 51 | 52 | attr:`radius` is an :class:`~kivy.properties.NumericProperty` 53 | and defaults to `'20dp'`. 54 | """ 55 | 56 | elevation = NumericProperty(3) 57 | """ 58 | Elevation of the widget.Elevation can be any number between -5 and +5(inclusive). 59 | Negative elevation will cause the widget to go into the screen whereas positive 60 | elevation will make it pop from the screen 61 | 62 | This widget has a default elevation of 3 63 | """ 64 | 65 | def __init__(self, **kwargs): 66 | super().__init__(**kwargs) 67 | Clock.schedule_once(self.elevation_set) 68 | 69 | def elevation_set(self, *args): 70 | self.elev = self.elevation 71 | self.bind(elevation=self.elevation_set) 72 | -------------------------------------------------------------------------------- /neukivy/uix/slider.py: -------------------------------------------------------------------------------- 1 | from kivy.clock import Clock 2 | from kivy.lang import Builder 3 | from kivy.properties import ColorProperty, ListProperty, NumericProperty, OptionProperty 4 | from kivy.uix.slider import Slider 5 | from kivymd.uix.selectioncontrol import Thumb 6 | from neukivy.uix.behaviors.neuglow import NeuGlowCircular 7 | from neukivy.uix.behaviors.neumorph import NeuMorphRoundedRectangle 8 | from neukivy.uix.behaviors.themeablebehavior import ThemeableBehavior 9 | 10 | Builder.load_string( 11 | """ 12 | 13 | 14 | canvas.before: 15 | Clear 16 | Color: 17 | Rectangle: 18 | size:self.light_shadow_size 19 | pos:self.light_shadow_pos 20 | texture:self.light_shadow 21 | Color: 22 | rgba:1,1,1,1 23 | Rectangle: 24 | size:self.dark_shadow_size 25 | pos:self.dark_shadow_pos 26 | texture:self.dark_shadow 27 | canvas: 28 | Color: 29 | rgba:(1,1,1,1) if self.elevation and self.elevation < 0 else self.comp_color 30 | RoundedRectangle: 31 | size:self.size 32 | pos:self.pos 33 | radius:self.height/2,self.height/2,self.height/2,self.height/2 34 | texture:self.border_texture if self.elevation and self.elevation < 0 else None 35 | Color: 36 | size_hint:None,None 37 | size:dp(400),dp(50) 38 | background_width:0 39 | cursor_size:0,0 40 | padding:self.height/2 41 | 42 | NeuThumb: 43 | canvas.before: 44 | Color: 45 | Rectangle: 46 | size:self.glow_size 47 | pos:self.glow_pos 48 | texture:self.glow_texture 49 | canvas: 50 | Color: 51 | rgba:root.thumb_color 52 | Ellipse: 53 | size:root.height-root.thumb_padding,root.height-root.thumb_padding 54 | pos:self.pos 55 | Color: 56 | size_hint: None, None 57 | size:root.height-root.thumb_padding,root.height-root.thumb_padding 58 | pos:root.value_pos[0]-self.width/2,root.center_y - self.height / 2 59 | behind_color: root.comp_color[0:3] +[0,] if root.thumb_bg_color==[0,0,0,0] else root.thumb_bg_color 60 | glow_color: root.thumb_color if root.glow_color==[0,0,0,0] else root.glow_color 61 | 62 | 63 | 64 | 65 | """ 66 | ) 67 | 68 | 69 | class NeuSlider(ThemeableBehavior, Slider, NeuMorphRoundedRectangle): 70 | 71 | comp_color = ListProperty([0, 0, 0, 0]) 72 | 73 | dark_color = ListProperty([0, 0, 0, 0]) 74 | 75 | light_color = ListProperty([0, 0, 0, 0]) 76 | 77 | elevation = NumericProperty(3) 78 | """ 79 | Elevation of the widget.Elevation can be any number between -5 and +5(inclusive). 80 | Negative elevation will cause the widget to go into the screen whereas positive 81 | elevation will make it pop from the screen 82 | 83 | This widget has a default elevation of 3 84 | """ 85 | 86 | radius = NumericProperty(0) 87 | """ 88 | Radius of the slider bar. The value defaults to half the height of the slider bar. 89 | 90 | attr:`radius` is an :class:`~kivy.properties.NumericProperty` 91 | and defaults to `'20dp'`. 92 | """ 93 | 94 | thumb_color = ColorProperty([0, 0, 0, 0]) 95 | """ 96 | Color of the thumb of the slider 97 | """ 98 | 99 | thumb_bg_color = ColorProperty([0, 0, 0, 0]) 100 | """ 101 | Color of background behind the thumb. This property is needed to 102 | properly display the glow effect. The property will default to the component 103 | color. But it can be manually set. If a color is manually set it will create a 104 | ring of that color around the thumb's glow. 105 | 106 | attr:`thumb_bg_color` is an :class:`~kivy.properties.ColorProperty` 107 | and defaults to the slider's 'comp_color'. 108 | """ 109 | 110 | thumb_padding = NumericProperty() 111 | """ 112 | The top and bottom padding value for the the thumb. This allows you to inset 113 | the thumb in the slider. It defaults to zero which means the thumb will be 114 | as big as the height of the slider. 115 | 116 | attr:`thumb_padding` is an :class:`~kivy.properties.NumericProperty` 117 | and defaults to the slider's '0'. 118 | """ 119 | 120 | glow_color = ColorProperty([0, 0, 0, 0]) 121 | """ 122 | Color of the glow behind the thumb. Defaults to the thumb's color. 123 | 124 | attr:`glow_color` is an :class:`~kivy.properties.ColorProperty` 125 | and defaults to '[0,0,0,0]' 126 | """ 127 | 128 | glow_radius = NumericProperty(20) 129 | """ 130 | Radius of the glow behind the thumb. 131 | 132 | attr:`glow_radius` is an :class:`~kivy.properties.NumericProperty` 133 | and defaults to '20'. 134 | """ 135 | 136 | def __init__(self, **kwargs): 137 | super().__init__(**kwargs) 138 | Clock.schedule_once(self.elevation_set) 139 | Clock.schedule_once(self.radius_set) 140 | 141 | def elevation_set(self, *args): 142 | self.elev = self.elevation 143 | 144 | def radius_set(self, *args): 145 | self.radius = self.height / 2 146 | 147 | 148 | class NeuThumb(ThemeableBehavior, Thumb, NeuGlowCircular): 149 | 150 | glow_radius = NumericProperty(20) 151 | 152 | glow_color = ColorProperty([0.8, 0.7, 0.5, 1]) 153 | 154 | def __init__(self, **kwargs): 155 | self.elev = self.elevation 156 | super().__init__(**kwargs) 157 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | setup( 3 | name='NeuKivy', 4 | url='https://github.com/Guhan-SenSam/NeuKivy', 5 | author='Guhan SenSam', 6 | author_email='infinium.software.2021@gmail.com', 7 | packages=find_packages(include=["neukivy", "neukivy.*"]), 8 | package_dir={"neukivy": "neukivy"}, 9 | package_data={ 10 | "neukivy": ["fonts/*.ttf"] 11 | }, 12 | install_requires=["kivy>=2.0.0", "pillow"], 13 | version='0.1', 14 | license='MIT', 15 | description='A collection of neumorphic widgets built with kivy' 16 | ) 17 | --------------------------------------------------------------------------------