From abcf173cc82fd635eea6be17d61b5a967f5b67a8 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 2 Oct 2022 17:16:59 +0200 Subject: [PATCH] Updated kivymd --- sbapp/kivymd/__init__.py | 26 +- sbapp/kivymd/_version.py | 5 - sbapp/kivymd/app.py | 12 +- sbapp/kivymd/color_definitions.py | 4 +- .../kivymd/data/glsl/elevation/elevation.frag | 44 + sbapp/kivymd/data/glsl/elevation/header.frag | 10 + sbapp/kivymd/data/glsl/elevation/main.frag | 10 + sbapp/kivymd/factory_registers.py | 12 +- .../fonts/materialdesignicons-webfont.ttf | Bin 1223172 -> 1243500 bytes sbapp/kivymd/icon_definitions.py | 156 +- sbapp/kivymd/images/firebase-logo.png | Bin 5436 -> 0 bytes sbapp/kivymd/images/folder.png | Bin 29440 -> 2311 bytes sbapp/kivymd/images/logo/kivymd-icon-128.png | Bin 0 -> 19826 bytes sbapp/kivymd/images/logo/kivymd-icon-256.png | Bin 0 -> 31943 bytes sbapp/kivymd/images/logo/kivymd-icon-512.png | Bin 0 -> 54349 bytes sbapp/kivymd/images/quad_shadow-0.png | Bin 31415 -> 0 bytes sbapp/kivymd/images/quad_shadow-1.png | Bin 31667 -> 0 bytes sbapp/kivymd/images/quad_shadow-2.png | Bin 21912 -> 0 bytes sbapp/kivymd/images/quad_shadow.atlas | 1 - sbapp/kivymd/images/rec_shadow-0.png | Bin 46593 -> 0 bytes sbapp/kivymd/images/rec_shadow-1.png | Bin 43957 -> 0 bytes sbapp/kivymd/images/rec_shadow.atlas | 1 - sbapp/kivymd/images/rec_st_shadow-0.png | Bin 30721 -> 0 bytes sbapp/kivymd/images/rec_st_shadow-1.png | Bin 32265 -> 0 bytes sbapp/kivymd/images/rec_st_shadow-2.png | Bin 28526 -> 0 bytes sbapp/kivymd/images/rec_st_shadow.atlas | 1 - sbapp/kivymd/images/restdb-logo.png | Bin 15475 -> 0 bytes sbapp/kivymd/images/round_shadow-0.png | Bin 39635 -> 0 bytes sbapp/kivymd/images/round_shadow-1.png | Bin 40767 -> 0 bytes sbapp/kivymd/images/round_shadow-2.png | Bin 26510 -> 0 bytes sbapp/kivymd/images/round_shadow.atlas | 1 - sbapp/kivymd/tests/base_test.py | 9 + .../pyinstaller/test_pyinstaller_packaging.py | 9 +- sbapp/kivymd/tests/test_backdrop.py | 24 + sbapp/kivymd/tests/test_bottom_navigation.py | 32 + sbapp/kivymd/tests/test_card.py | 25 + sbapp/kivymd/tests/test_chip.py | 16 + sbapp/kivymd/tests/test_create_project.py | 7 +- sbapp/kivymd/tests/test_fitimage.py | 24 + sbapp/kivymd/tests/test_imagelist.py | 39 + sbapp/kivymd/tests/test_list.py | 67 + sbapp/kivymd/tests/test_navigationdrawer.py | 94 + sbapp/kivymd/tests/test_tab.py | 14 + sbapp/kivymd/tests/test_textfield.py | 72 + sbapp/kivymd/theming.py | 1045 +++++--- sbapp/kivymd/tools/hotreload/app.py | 5 +- .../packaging/pyinstaller/hook-kivymd.py | 12 + .../tools/patterns/MVC/Controller/__init__.py | 0 .../MVC/Controller/first_screen.py_tmp | 3 - sbapp/kivymd/tools/patterns/MVC/Makefile.tmp | 26 - .../patterns/MVC/Model/base_model.py_tmp | 33 - .../patterns/MVC/Model/first_screen.py_tmp | 1 - .../tools/patterns/MVC/Utility/__init__.py | 0 .../patterns/MVC/Utility/observer.py_tmp | 16 - .../patterns/MVC/View/FirstScreen/__init__.py | 0 .../MVC/View/FirstScreen/first_screen.kv | 72 - .../MVC/View/FirstScreen/first_screen.py_tmp | 15 - .../patterns/MVC/View/base_screen.py_tmp | 47 - .../tools/patterns/MVC/View/screens.py_tmp | 13 - sbapp/kivymd/tools/patterns/MVC/main.py_tmp | 54 - sbapp/kivymd/tools/patterns/add_view.py | 210 ++ sbapp/kivymd/tools/patterns/create_project.py | 1130 +++++---- sbapp/kivymd/tools/release/git_commands.py | 2 +- sbapp/kivymd/tools/release/make_release.py | 2 +- sbapp/kivymd/tools/release/update_icons.py | 2 +- sbapp/kivymd/uix/anchorlayout.py | 68 +- sbapp/kivymd/uix/backdrop/backdrop.kv | 4 +- sbapp/kivymd/uix/backdrop/backdrop.py | 220 +- sbapp/kivymd/uix/banner/banner.py | 5 +- sbapp/kivymd/uix/behaviors/__init__.py | 12 +- .../uix/behaviors/backgroundcolor_behavior.py | 56 +- .../uix/behaviors/declarative_behavior.py | 317 +++ sbapp/kivymd/uix/behaviors/elevation.py | 2099 ++++++----------- sbapp/kivymd/uix/behaviors/focus_behavior.py | 28 +- sbapp/kivymd/uix/behaviors/ripple_behavior.py | 2 +- sbapp/kivymd/uix/behaviors/rotate_behavior.py | 133 ++ sbapp/kivymd/uix/behaviors/scale_behavior.py | 156 ++ .../kivymd/uix/behaviors/stencil_behavior.py | 134 ++ sbapp/kivymd/uix/behaviors/toggle_behavior.py | 134 +- .../uix/bottomnavigation/bottomnavigation.kv | 5 +- .../uix/bottomnavigation/bottomnavigation.py | 256 +- sbapp/kivymd/uix/bottomsheet/bottomsheet.py | 6 +- sbapp/kivymd/uix/boxlayout.py | 14 +- sbapp/kivymd/uix/button/__init__.py | 1 + sbapp/kivymd/uix/button/button.kv | 99 +- sbapp/kivymd/uix/button/button.py | 1031 +++++--- sbapp/kivymd/uix/card/card.kv | 11 - sbapp/kivymd/uix/card/card.py | 852 ++++--- sbapp/kivymd/uix/carousel.py | 8 +- sbapp/kivymd/uix/chip/__init__.py | 2 +- sbapp/kivymd/uix/chip/chip.py | 77 +- .../uix/controllers/windowcontroller.py | 23 +- sbapp/kivymd/uix/datatables/datatables.kv | 19 +- sbapp/kivymd/uix/datatables/datatables.py | 176 +- sbapp/kivymd/uix/dialog/dialog.kv | 7 +- sbapp/kivymd/uix/dialog/dialog.py | 238 +- sbapp/kivymd/uix/dropdownitem/dropdownitem.kv | 5 +- sbapp/kivymd/uix/dropdownitem/dropdownitem.py | 15 +- .../uix/expansionpanel/expansionpanel.py | 6 +- sbapp/kivymd/uix/filemanager/filemanager.kv | 36 +- sbapp/kivymd/uix/filemanager/filemanager.py | 328 ++- sbapp/kivymd/uix/fitimage/fitimage.py | 139 +- sbapp/kivymd/uix/floatlayout.py | 12 +- sbapp/kivymd/uix/gridlayout.py | 8 +- sbapp/kivymd/uix/hero.py | 142 +- sbapp/kivymd/uix/imagelist/__init__.py | 2 +- sbapp/kivymd/uix/imagelist/imagelist.py | 41 +- sbapp/kivymd/uix/label/label.kv | 10 +- sbapp/kivymd/uix/label/label.py | 75 +- sbapp/kivymd/uix/list/__init__.py | 1 - sbapp/kivymd/uix/list/list.py | 1026 +++++--- sbapp/kivymd/uix/menu/menu.kv | 6 +- sbapp/kivymd/uix/menu/menu.py | 6 +- .../uix/navigationdrawer/navigationdrawer.py | 720 +++--- sbapp/kivymd/uix/navigationrail/__init__.py | 1 + .../uix/navigationrail/navigationrail.kv | 34 +- .../uix/navigationrail/navigationrail.py | 661 ++++-- .../uix/pickers/datepicker/datepicker.py | 421 +++- .../uix/pickers/timepicker/timepicker.py | 117 +- sbapp/kivymd/uix/progressbar/progressbar.py | 2 +- sbapp/kivymd/uix/recyclegridlayout.py | 26 +- sbapp/kivymd/uix/recycleview.py | 44 + .../kivymd/uix/refreshlayout/refreshlayout.py | 31 +- sbapp/kivymd/uix/relativelayout.py | 16 +- sbapp/kivymd/uix/responsivelayout.py | 189 ++ sbapp/kivymd/uix/screen.py | 38 +- sbapp/kivymd/uix/screenmanager.py | 87 +- sbapp/kivymd/uix/scrollview.py | 49 + .../uix/segmentedcontrol/segmentedcontrol.kv | 5 +- .../uix/segmentedcontrol/segmentedcontrol.py | 139 +- sbapp/kivymd/uix/selection/selection.py | 7 +- .../uix/selectioncontrol/selectioncontrol.kv | 117 +- .../uix/selectioncontrol/selectioncontrol.py | 560 +++-- sbapp/kivymd/uix/slider/slider.kv | 133 +- sbapp/kivymd/uix/slider/slider.py | 307 ++- sbapp/kivymd/uix/sliverappbar/sliverappbar.kv | 2 +- sbapp/kivymd/uix/sliverappbar/sliverappbar.py | 58 +- sbapp/kivymd/uix/snackbar/snackbar.kv | 2 +- sbapp/kivymd/uix/snackbar/snackbar.py | 9 +- sbapp/kivymd/uix/stacklayout.py | 14 +- sbapp/kivymd/uix/swiper/swiper.py | 25 +- sbapp/kivymd/uix/tab/tab.kv | 4 + sbapp/kivymd/uix/tab/tab.py | 1121 ++++++--- sbapp/kivymd/uix/taptargetview.py | 172 +- .../templates/rotatewidget/rotatewidget.kv | 9 - .../templates/rotatewidget/rotatewidget.py | 130 +- .../uix/templates/scalewidget/scalewidget.kv | 10 - .../uix/templates/scalewidget/scalewidget.py | 150 +- .../templates/stencilwidget/stencilwidget.kv | 19 - .../templates/stencilwidget/stencilwidget.py | 118 +- sbapp/kivymd/uix/textfield/__init__.py | 2 +- sbapp/kivymd/uix/textfield/textfield.kv | 125 +- sbapp/kivymd/uix/textfield/textfield.py | 693 ++++-- sbapp/kivymd/uix/toolbar/__init__.py | 2 +- sbapp/kivymd/uix/toolbar/toolbar.kv | 7 +- sbapp/kivymd/uix/toolbar/toolbar.py | 65 +- sbapp/kivymd/uix/tooltip/tooltip.py | 26 +- sbapp/kivymd/uix/transition/transition.py | 211 +- sbapp/kivymd/uix/widget.py | 11 +- sbapp/kivymd/utils/fitimage.py | 19 - 160 files changed, 11617 insertions(+), 6545 deletions(-) delete mode 100644 sbapp/kivymd/_version.py create mode 100644 sbapp/kivymd/data/glsl/elevation/elevation.frag create mode 100644 sbapp/kivymd/data/glsl/elevation/header.frag create mode 100644 sbapp/kivymd/data/glsl/elevation/main.frag delete mode 100644 sbapp/kivymd/images/firebase-logo.png create mode 100644 sbapp/kivymd/images/logo/kivymd-icon-128.png create mode 100644 sbapp/kivymd/images/logo/kivymd-icon-256.png create mode 100644 sbapp/kivymd/images/logo/kivymd-icon-512.png delete mode 100644 sbapp/kivymd/images/quad_shadow-0.png delete mode 100644 sbapp/kivymd/images/quad_shadow-1.png delete mode 100644 sbapp/kivymd/images/quad_shadow-2.png delete mode 100644 sbapp/kivymd/images/quad_shadow.atlas delete mode 100644 sbapp/kivymd/images/rec_shadow-0.png delete mode 100644 sbapp/kivymd/images/rec_shadow-1.png delete mode 100644 sbapp/kivymd/images/rec_shadow.atlas delete mode 100644 sbapp/kivymd/images/rec_st_shadow-0.png delete mode 100644 sbapp/kivymd/images/rec_st_shadow-1.png delete mode 100644 sbapp/kivymd/images/rec_st_shadow-2.png delete mode 100644 sbapp/kivymd/images/rec_st_shadow.atlas delete mode 100644 sbapp/kivymd/images/restdb-logo.png delete mode 100644 sbapp/kivymd/images/round_shadow-0.png delete mode 100644 sbapp/kivymd/images/round_shadow-1.png delete mode 100644 sbapp/kivymd/images/round_shadow-2.png delete mode 100644 sbapp/kivymd/images/round_shadow.atlas create mode 100644 sbapp/kivymd/tests/base_test.py create mode 100644 sbapp/kivymd/tests/test_backdrop.py create mode 100644 sbapp/kivymd/tests/test_bottom_navigation.py create mode 100644 sbapp/kivymd/tests/test_card.py create mode 100644 sbapp/kivymd/tests/test_chip.py create mode 100644 sbapp/kivymd/tests/test_fitimage.py create mode 100644 sbapp/kivymd/tests/test_imagelist.py create mode 100644 sbapp/kivymd/tests/test_list.py create mode 100644 sbapp/kivymd/tests/test_navigationdrawer.py create mode 100644 sbapp/kivymd/tests/test_tab.py create mode 100644 sbapp/kivymd/tests/test_textfield.py delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Controller/__init__.py delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Controller/first_screen.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Makefile.tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Model/base_model.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Model/first_screen.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Utility/__init__.py delete mode 100644 sbapp/kivymd/tools/patterns/MVC/Utility/observer.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/__init__.py delete mode 100644 sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.kv delete mode 100644 sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/View/base_screen.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/View/screens.py_tmp delete mode 100644 sbapp/kivymd/tools/patterns/MVC/main.py_tmp create mode 100644 sbapp/kivymd/tools/patterns/add_view.py create mode 100644 sbapp/kivymd/uix/behaviors/declarative_behavior.py create mode 100644 sbapp/kivymd/uix/behaviors/rotate_behavior.py create mode 100644 sbapp/kivymd/uix/behaviors/scale_behavior.py create mode 100644 sbapp/kivymd/uix/behaviors/stencil_behavior.py create mode 100644 sbapp/kivymd/uix/recycleview.py create mode 100644 sbapp/kivymd/uix/responsivelayout.py create mode 100644 sbapp/kivymd/uix/scrollview.py delete mode 100644 sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv delete mode 100644 sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv delete mode 100644 sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv delete mode 100644 sbapp/kivymd/utils/fitimage.py diff --git a/sbapp/kivymd/__init__.py b/sbapp/kivymd/__init__.py index f94c63c..fa6cb27 100644 --- a/sbapp/kivymd/__init__.py +++ b/sbapp/kivymd/__init__.py @@ -9,23 +9,16 @@ Is a collection of Material Design compliant widgets for use with, a framework for cross-platform, touch-enabled graphical applications. The project's goal is to approximate Google's `Material Design spec `_ as close as possible without -sacrificing ease of use or application performance. +sacrificing ease of use. This library is a fork of the `KivyMD project -`_ the author of which stopped supporting -this project three years ago. We found the strength and brought this project -to a new level. Currently we're in **beta** status, so things are changing -all the time and we cannot promise any kind of API stability. -However it is safe to vendor now and make use of what's currently available. +`_. We found the strength and brought this +project to a new level. -Join the project! Just fork the project, branch out and submit a pull request -when your patch is ready. If any changes are necessary, we'll guide you -through the steps that need to be done via PR comments or access to your for -may be requested to outright submit them. If you wish to become a project -developer (permission to create branches on the project without forking for -easier collaboration), have at least one PR approved and ask for it. -If you contribute regularly to the project the role may be offered to you -without asking too. +If you wish to become a project developer (permission to create branches on the +project without forking for easier collaboration), have at least one PR +approved and ask for it. If you contribute regularly to the project the role +may be offered to you without asking too. """ import os @@ -33,7 +26,7 @@ import os import kivy from kivy.logger import Logger -__version__ = "1.0.0.dev0" +__version__ = "1.1.0.dev0" """KivyMD version.""" release = False @@ -56,6 +49,9 @@ images_path = os.path.join(path, f"images{os.sep}") uix_path = os.path.join(path, "uix") """Path to uix directory.""" +glsl_path = os.path.join(path, "data", "glsl") +"""Path to glsl directory.""" + _log_message = ( "KivyMD:" + (" Release" if release else "") diff --git a/sbapp/kivymd/_version.py b/sbapp/kivymd/_version.py deleted file mode 100644 index 5faa449..0000000 --- a/sbapp/kivymd/_version.py +++ /dev/null @@ -1,5 +0,0 @@ -# THIS FILE IS GENERATED FROM KIVYMD SETUP.PY -__version__ = '1.0.0.dev0' -__hash__ = '68ec8626a93b0e7f69e48d9755c4af70028f66a2' -__short_hash__ = '68ec862' -__date__ = '2022-07-07' diff --git a/sbapp/kivymd/app.py b/sbapp/kivymd/app.py index abae5f0..5c7b207 100644 --- a/sbapp/kivymd/app.py +++ b/sbapp/kivymd/app.py @@ -45,7 +45,7 @@ import os from kivy.app import App from kivy.lang import Builder from kivy.logger import Logger -from kivy.properties import ObjectProperty +from kivy.properties import ObjectProperty, StringProperty from kivymd.theming import ThemeManager @@ -71,6 +71,16 @@ class MDApp(App, FpsMonitoring): information. """ + icon = StringProperty("kivymd/images/logo/kivymd-icon-512.png") + """ + See :attr:`~kivy.app.App.icon` attribute for more information. + + .. versionadded:: 1.1.0 + + :attr:`icon` is an :class:`~kivy.properties.StringProperty` + adn default to `kivymd/images/logo/kivymd-icon-512.png`. + """ + theme_cls = ObjectProperty() """ Instance of :class:`~ThemeManager` class. diff --git a/sbapp/kivymd/color_definitions.py b/sbapp/kivymd/color_definitions.py index 47a2edf..e4a7de4 100755 --- a/sbapp/kivymd/color_definitions.py +++ b/sbapp/kivymd/color_definitions.py @@ -412,7 +412,7 @@ To demonstrate the shades of the palette, you can run the following code: self.screen = Factory.Root() for name_tab in colors.keys(): - tab = Tab(text=name_tab) + tab = Tab(title=name_tab) self.screen.ids.android_tabs.add_widget(tab) return self.screen @@ -427,7 +427,7 @@ To demonstrate the shades of the palette, you can run the following code: { "viewclass": "ItemColor", "md_bg_color": colors[tab_text][value_color], - "text": value_color, + "title": value_color, } ) diff --git a/sbapp/kivymd/data/glsl/elevation/elevation.frag b/sbapp/kivymd/data/glsl/elevation/elevation.frag new file mode 100644 index 0000000..9f28abf --- /dev/null +++ b/sbapp/kivymd/data/glsl/elevation/elevation.frag @@ -0,0 +1,44 @@ +/* +The shader code has been refactored for the KivyMD library. +You can find the original code of this shaders at the links: + +https://www.shadertoy.com/view/WtdSDs +https://www.shadertoy.com/view/fsdyzB + +Additional thanks to iq for optimizing conditional block for individual +corner radius: +https://iquilezles.org/articles/distfunctions +*/ + +float roundedBoxSDF(vec2 centerPosition, vec2 size, vec4 radius) { + radius.xy = (centerPosition.x > 0.0) ? radius.xy : radius.zw; + radius.x = (centerPosition.y > 0.0) ? radius.x : radius.y; + + vec2 q = abs(centerPosition) - (size - shadow_softness) + radius.x; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius.x; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + // Smooth the result (free antialiasing). + float edge0 = 0.0; + float smoothedAlpha = 1.0 - smoothstep(0.0, edge0, 1.0); + // Get the resultant shape. + vec4 quadColor = mix( + vec4( + shadow_color[0], + shadow_color[1], + shadow_color[2], + 0.0 + ), + shadow_color, + smoothedAlpha + ); + // Apply a drop shadow effect. + float shadowDistance = roundedBoxSDF( + fragCoord.xy - mouse.xy - (size / 2.0), size / 2.0, shadow_radius + ); + float shadowAlpha = 1.0 - smoothstep( + -shadow_softness, shadow_softness, shadowDistance + ); + fragColor = mix(quadColor, shadow_color, shadowAlpha - smoothedAlpha); +} diff --git a/sbapp/kivymd/data/glsl/elevation/header.frag b/sbapp/kivymd/data/glsl/elevation/header.frag new file mode 100644 index 0000000..acb3a8a --- /dev/null +++ b/sbapp/kivymd/data/glsl/elevation/header.frag @@ -0,0 +1,10 @@ +#ifdef GL_ES + precision highp float; +#endif + +uniform vec4 resolution; +uniform vec4 mouse; +uniform vec2 size; +uniform vec4 shadow_radius; +uniform float shadow_softness; +uniform vec4 shadow_color; diff --git a/sbapp/kivymd/data/glsl/elevation/main.frag b/sbapp/kivymd/data/glsl/elevation/main.frag new file mode 100644 index 0000000..d9b8712 --- /dev/null +++ b/sbapp/kivymd/data/glsl/elevation/main.frag @@ -0,0 +1,10 @@ +vec2 gfc(in vec4 fc) { + vec2 canvas_pos = resolution.zw; + vec2 uv = fc.xy; + uv.y -= canvas_pos.y; + return uv; +} + +void main(void) { + mainImage(gl_FragColor, gfc(gl_FragCoord)); +} diff --git a/sbapp/kivymd/factory_registers.py b/sbapp/kivymd/factory_registers.py index 57efb1a..1a20b01 100644 --- a/sbapp/kivymd/factory_registers.py +++ b/sbapp/kivymd/factory_registers.py @@ -5,11 +5,15 @@ Register KivyMD widgets to use without import. from kivy.factory import Factory register = Factory.register +register("MDScrollView", module="kivymd.uix.scrollview") +register("MDRecycleView", module="kivymd.uix.recycleview") +register("MDResponsiveLayout", module="kivymd.uix.responsivelayout") register("MDSegmentedControl", module="kivymd.uix.segmentedcontrol") register("MDSegmentedControlItem", module="kivymd.uix.segmentedcontrol") register("MDSliverAppbar", module="kivymd.uix.sliverappbar") register("MDSliverAppbarContent", module="kivymd.uix.sliverappbar") register("MDSliverAppbarHeader", module="kivymd.uix.sliverappbar") +register("MDNavigationRailItem", module="kivymd.uix.navigationrail") register("MDNavigationRail", module="kivymd.uix.navigationrail") register("MDNavigationRailFabButton", module="kivymd.uix.navigationrail") register("MDNavigationRailMenuButton", module="kivymd.uix.navigationrail") @@ -29,7 +33,7 @@ register("MDExpansionPanel", module="kivymd.uix.expansionpanel") register("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") register("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") register("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") -register("FitImage", module="kivymd.utils.fitimage") +register("FitImage", module="kivymd.uix.fitimage") register("MDBackdrop", module="kivymd.uix.backdrop") register("MDBanner", module="kivymd.uix.banner") register("MDTooltip", module="kivymd.uix.tooltip") @@ -54,10 +58,7 @@ register("MDCard", module="kivymd.uix.card") register("MDSeparator", module="kivymd.uix.card") register("MDSelectionList", module="kivymd.uix.selection") register("MDChip", module="kivymd.uix.chip") -register("MDChooseChip", module="kivymd.uix.chip") register("MDSmartTile", module="kivymd.uix.imagelist") -register("SmartTileWithLabel", module="kivymd.uix.imagelist") -register("SmartTileWithStar", module="kivymd.uix.imagelist") register("MDLabel", module="kivymd.uix.label") register("MDIcon", module="kivymd.uix.label") register("MDList", module="kivymd.uix.list") @@ -65,7 +66,6 @@ register("ILeftBody", module="kivymd.uix.list") register("ILeftBodyTouch", module="kivymd.uix.list") register("IRightBody", module="kivymd.uix.list") register("IRightBodyTouch", module="kivymd.uix.list") -register("ContainerSupport", module="kivymd.uix.list") register("OneLineListItem", module="kivymd.uix.list") register("TwoLineListItem", module="kivymd.uix.list") register("ThreeLineListItem", module="kivymd.uix.list") @@ -99,9 +99,7 @@ register("MDSlider", module="kivymd.uix.slider") register("MDSpinner", module="kivymd.uix.spinner") register("MDTabs", module="kivymd.uix.tab") register("MDTextField", module="kivymd.uix.textfield") -register("MDTextFieldRound", module="kivymd.uix.textfield") register("MDTextFieldRect", module="kivymd.uix.textfield") -register("MDToolbar", module="kivymd.uix.toolbar") register("MDTopAppBar", module="kivymd.uix.toolbar") register("MDBottomAppBar", module="kivymd.uix.toolbar") register("MDDropDownItem", module="kivymd.uix.dropdownitem") diff --git a/sbapp/kivymd/fonts/materialdesignicons-webfont.ttf b/sbapp/kivymd/fonts/materialdesignicons-webfont.ttf index 162b7b69ee2ea4a2bbc84127707c2756373ff4c5..d685510874af238852c0c9fec81b9c5b0f870fce 100644 GIT binary patch delta 48131 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 delta 30400 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 Djqn(; diff --git a/sbapp/kivymd/icon_definitions.py b/sbapp/kivymd/icon_definitions.py index 14cd8eb..3941eae 100755 --- a/sbapp/kivymd/icon_definitions.py +++ b/sbapp/kivymd/icon_definitions.py @@ -12,7 +12,7 @@ Themes/Icon Definitions List of icons from materialdesignicons.com. These expanded material design icons are maintained by Austin Andrews (Templarian on Github). -LAST UPDATED: Version 6.9.96 +LAST UPDATED: Version 7.0.96 To preview the icons and their names, you can use the following application: ---------------------------------------------------------------------------- @@ -154,6 +154,8 @@ md_icons = { "account-box-outline": "\U000F0007", "account-cancel": "\U000F12DF", "account-cancel-outline": "\U000F12E0", + "account-card": "\U000F1BA4", + "account-card-outline": "\U000F1BA5", "account-cash": "\U000F1097", "account-cash-outline": "\U000F1098", "account-check": "\U000F0008", @@ -171,6 +173,8 @@ md_icons = { "account-convert-outline": "\U000F1301", "account-cowboy-hat": "\U000F0E9B", "account-cowboy-hat-outline": "\U000F17F3", + "account-credit-card": "\U000F1BA6", + "account-credit-card-outline": "\U000F1BA7", "account-details": "\U000F0631", "account-details-outline": "\U000F1372", "account-edit": "\U000F06BC", @@ -260,6 +264,7 @@ md_icons = { "air-humidifier": "\U000F1099", "air-humidifier-off": "\U000F1466", "air-purifier": "\U000F0D44", + "air-purifier-off": "\U000F1B57", "airbag": "\U000F0BE9", "airballoon": "\U000F001C", "airballoon-outline": "\U000F100B", @@ -480,7 +485,6 @@ md_icons = { "amplifier-off": "\U000F11B5", "anchor": "\U000F0031", "android": "\U000F0032", - "android-messages": "\U000F0D45", "android-studio": "\U000F0034", "angle-acute": "\U000F0937", "angle-obtuse": "\U000F0938", @@ -745,6 +749,7 @@ md_icons = { "arrow-up-thin": "\U000F19B2", "arrow-up-thin-circle-outline": "\U000F1597", "arrow-vertical-lock": "\U000F115C", + "artboard": "\U000F1B9A", "artstation": "\U000F0B5B", "aspect-ratio": "\U000F0A24", "assistant": "\U000F0064", @@ -762,6 +767,7 @@ md_icons = { "attachment-off": "\U000F1AC3", "attachment-plus": "\U000F1AC4", "attachment-remove": "\U000F1AC5", + "atv": "\U000F1B70", "audio-input-rca": "\U000F186B", "audio-input-stereo-minijack": "\U000F186C", "audio-input-xlr": "\U000F186D", @@ -774,6 +780,8 @@ md_icons = { "autorenew": "\U000F006A", "autorenew-off": "\U000F19E7", "av-timer": "\U000F006B", + "awning": "\U000F1B87", + "awning-outline": "\U000F1B88", "aws": "\U000F0E0F", "axe": "\U000F08C8", "axe-battle": "\U000F1842", @@ -987,6 +995,7 @@ md_icons = { "beaker-remove": "\U000F1233", "beaker-remove-outline": "\U000F1234", "bed": "\U000F02E3", + "bed-clock": "\U000F1B94", "bed-double": "\U000F0FD4", "bed-double-outline": "\U000F0FD3", "bed-empty": "\U000F08A0", @@ -1148,10 +1157,11 @@ md_icons = { "book-sync": "\U000F169C", "book-sync-outline": "\U000F16C8", "book-variant": "\U000F00BF", - "book-variant-multiple": "\U000F00BC", "bookmark": "\U000F00C0", + "bookmark-box": "\U000F1B75", "bookmark-box-multiple": "\U000F196C", "bookmark-box-multiple-outline": "\U000F196D", + "bookmark-box-outline": "\U000F1B76", "bookmark-check": "\U000F00C1", "bookmark-check-outline": "\U000F137B", "bookmark-minus": "\U000F09CC", @@ -1339,8 +1349,11 @@ md_icons = { "calendar-account": "\U000F0ED7", "calendar-account-outline": "\U000F0ED8", "calendar-alert": "\U000F0A31", + "calendar-alert-outline": "\U000F1B62", "calendar-arrow-left": "\U000F1134", "calendar-arrow-right": "\U000F1135", + "calendar-badge": "\U000F1B9D", + "calendar-badge-outline": "\U000F1B9E", "calendar-blank": "\U000F00EE", "calendar-blank-multiple": "\U000F1073", "calendar-blank-outline": "\U000F0B66", @@ -1349,26 +1362,40 @@ md_icons = { "calendar-clock": "\U000F00F0", "calendar-clock-outline": "\U000F16E1", "calendar-collapse-horizontal": "\U000F189D", + "calendar-collapse-horizontal-outline": "\U000F1B63", "calendar-cursor": "\U000F157B", + "calendar-cursor-outline": "\U000F1B64", "calendar-edit": "\U000F08A7", + "calendar-edit-outline": "\U000F1B65", "calendar-end": "\U000F166C", + "calendar-end-outline": "\U000F1B66", "calendar-expand-horizontal": "\U000F189E", + "calendar-expand-horizontal-outline": "\U000F1B67", "calendar-export": "\U000F0B24", + "calendar-export-outline": "\U000F1B68", "calendar-filter": "\U000F1A32", "calendar-filter-outline": "\U000F1A33", "calendar-heart": "\U000F09D2", + "calendar-heart-outline": "\U000F1B69", "calendar-import": "\U000F0B25", + "calendar-import-outline": "\U000F1B6A", "calendar-lock": "\U000F1641", + "calendar-lock-open": "\U000F1B5B", + "calendar-lock-open-outline": "\U000F1B5C", "calendar-lock-outline": "\U000F1642", "calendar-minus": "\U000F0D5C", + "calendar-minus-outline": "\U000F1B6B", "calendar-month": "\U000F0E17", "calendar-month-outline": "\U000F0E18", "calendar-multiple": "\U000F00F1", "calendar-multiple-check": "\U000F00F2", "calendar-multiselect": "\U000F0A32", + "calendar-multiselect-outline": "\U000F1B55", "calendar-outline": "\U000F0B67", "calendar-plus": "\U000F00F3", + "calendar-plus-outline": "\U000F1B6C", "calendar-question": "\U000F0692", + "calendar-question-outline": "\U000F1B6D", "calendar-range": "\U000F0679", "calendar-range-outline": "\U000F0B68", "calendar-refresh": "\U000F01E1", @@ -1376,9 +1403,11 @@ md_icons = { "calendar-remove": "\U000F00F4", "calendar-remove-outline": "\U000F0C45", "calendar-search": "\U000F094C", + "calendar-search-outline": "\U000F1B6E", "calendar-star": "\U000F09D3", "calendar-star-outline": "\U000F1B53", "calendar-start": "\U000F166D", + "calendar-start-outline": "\U000F1B6F", "calendar-sync": "\U000F0E8E", "calendar-sync-outline": "\U000F0E8F", "calendar-text": "\U000F00F5", @@ -1496,6 +1525,8 @@ md_icons = { "car-outline": "\U000F14ED", "car-parking-lights": "\U000F0D63", "car-pickup": "\U000F07AA", + "car-search": "\U000F1B8D", + "car-search-outline": "\U000F1B8E", "car-seat": "\U000F0FA4", "car-seat-cooler": "\U000F0FA5", "car-seat-heater": "\U000F0FA6", @@ -1584,6 +1615,7 @@ md_icons = { "cart-minus": "\U000F0D68", "cart-off": "\U000F066B", "cart-outline": "\U000F0111", + "cart-percent": "\U000F1BAE", "cart-plus": "\U000F0112", "cart-remove": "\U000F0D69", "cart-variant": "\U000F15EB", @@ -1969,6 +2001,7 @@ md_icons = { "cloud-tags": "\U000F07B6", "cloud-upload": "\U000F0167", "cloud-upload-outline": "\U000F0B7E", + "clouds": "\U000F1B95", "clover": "\U000F0816", "coach-lamp": "\U000F1020", "coach-lamp-variant": "\U000F1A37", @@ -2128,8 +2161,10 @@ md_icons = { "contrast": "\U000F0195", "contrast-box": "\U000F0196", "contrast-circle": "\U000F0197", + "controller": "\U000F02B4", "controller-classic": "\U000F0B82", "controller-classic-outline": "\U000F0B83", + "controller-off": "\U000F02B5", "cookie": "\U000F0198", "cookie-alert": "\U000F16D0", "cookie-alert-outline": "\U000F16D1", @@ -2278,6 +2313,7 @@ md_icons = { "currency-sign": "\U000F07BE", "currency-try": "\U000F01B2", "currency-twd": "\U000F07BF", + "currency-uah": "\U000F1B9B", "currency-usd": "\U000F01C1", "currency-usd-off": "\U000F067A", "current-ac": "\U000F1480", @@ -2391,8 +2427,6 @@ md_icons = { "desk-lamp-on": "\U000F1B20", "deskphone": "\U000F01C3", "desktop-classic": "\U000F07C0", - "desktop-mac": "\U000F01C4", - "desktop-mac-dashboard": "\U000F09E8", "desktop-tower": "\U000F01C5", "desktop-tower-monitor": "\U000F0AAB", "details": "\U000F01C6", @@ -2442,7 +2476,6 @@ md_icons = { "disc": "\U000F05EE", "disc-alert": "\U000F01D1", "disc-player": "\U000F0960", - "discord": "\U000F066F", "dishwasher": "\U000F0AAC", "dishwasher-alert": "\U000F11B8", "dishwasher-off": "\U000F11B9", @@ -2457,8 +2490,9 @@ md_icons = { "diving": "\U000F1977", "diving-flippers": "\U000F0DBF", "diving-helmet": "\U000F0DC0", - "diving-scuba": "\U000F0DC1", + "diving-scuba": "\U000F1B77", "diving-scuba-flag": "\U000F0DC2", + "diving-scuba-mask": "\U000F0DC1", "diving-scuba-tank": "\U000F0DC3", "diving-scuba-tank-multiple": "\U000F0DC4", "diving-snorkel": "\U000F0DC5", @@ -2590,6 +2624,10 @@ md_icons = { "email": "\U000F01EE", "email-alert": "\U000F06CF", "email-alert-outline": "\U000F0D42", + "email-arrow-left": "\U000F10DA", + "email-arrow-left-outline": "\U000F10DB", + "email-arrow-right": "\U000F10DC", + "email-arrow-right-outline": "\U000F10DD", "email-box": "\U000F0D03", "email-check": "\U000F0AB1", "email-check-outline": "\U000F0AB2", @@ -2598,6 +2636,7 @@ md_icons = { "email-fast": "\U000F186F", "email-fast-outline": "\U000F1870", "email-lock": "\U000F01F1", + "email-lock-outline": "\U000F1B61", "email-mark-as-unread": "\U000F0B92", "email-minus": "\U000F0EE5", "email-minus-outline": "\U000F0EE6", @@ -2613,16 +2652,12 @@ md_icons = { "email-outline": "\U000F01F0", "email-plus": "\U000F09EB", "email-plus-outline": "\U000F09EC", - "email-receive": "\U000F10DA", - "email-receive-outline": "\U000F10DB", "email-remove": "\U000F1661", "email-remove-outline": "\U000F1662", "email-seal": "\U000F195B", "email-seal-outline": "\U000F195C", "email-search": "\U000F0961", "email-search-outline": "\U000F0962", - "email-send": "\U000F10DC", - "email-send-outline": "\U000F10DD", "email-sync": "\U000F12C7", "email-sync-outline": "\U000F12C8", "email-variant": "\U000F05F0", @@ -3013,7 +3048,15 @@ md_icons = { "flag-remove-outline": "\U000F10B4", "flag-triangle": "\U000F023F", "flag-variant": "\U000F0240", + "flag-variant-minus": "\U000F1BB4", + "flag-variant-minus-outline": "\U000F1BB5", + "flag-variant-off": "\U000F1BB0", + "flag-variant-off-outline": "\U000F1BB1", "flag-variant-outline": "\U000F023E", + "flag-variant-plus": "\U000F1BB2", + "flag-variant-plus-outline": "\U000F1BB3", + "flag-variant-remove": "\U000F1BB6", + "flag-variant-remove-outline": "\U000F1BB7", "flare": "\U000F0D72", "flash": "\U000F0241", "flash-alert": "\U000F0EF7", @@ -3275,6 +3318,7 @@ md_icons = { "format-list-checkbox": "\U000F096A", "format-list-checks": "\U000F0756", "format-list-group": "\U000F1860", + "format-list-group-plus": "\U000F1B56", "format-list-numbered": "\U000F027B", "format-list-numbered-rtl": "\U000F0D0D", "format-list-text": "\U000F126F", @@ -3285,6 +3329,8 @@ md_icons = { "format-paragraph": "\U000F027D", "format-paragraph-spacing": "\U000F1AFD", "format-pilcrow": "\U000F06D8", + "format-pilcrow-arrow-left": "\U000F0286", + "format-pilcrow-arrow-right": "\U000F0285", "format-quote-close": "\U000F027E", "format-quote-close-outline": "\U000F11A8", "format-quote-open": "\U000F0757", @@ -3310,8 +3356,6 @@ md_icons = { "format-text-wrapping-overflow": "\U000F0D0F", "format-text-wrapping-wrap": "\U000F0D10", "format-textbox": "\U000F0D11", - "format-textdirection-l-to-r": "\U000F0285", - "format-textdirection-r-to-l": "\U000F0286", "format-title": "\U000F05F4", "format-underline": "\U000F0287", "format-underline-wavy": "\U000F18E9", @@ -3487,7 +3531,8 @@ md_icons = { "glass-tulip": "\U000F02A8", "glass-wine": "\U000F0876", "glasses": "\U000F02AA", - "globe-light": "\U000F12D7", + "globe-light": "\U000F066F", + "globe-light-outline": "\U000F12D7", "globe-model": "\U000F08E9", "gmail": "\U000F02AB", "gnome": "\U000F02AC", @@ -3512,15 +3557,12 @@ md_icons = { "google-circles-group": "\U000F02B3", "google-classroom": "\U000F02C0", "google-cloud": "\U000F11F6", - "google-controller": "\U000F02B4", - "google-controller-off": "\U000F02B5", "google-downasaur": "\U000F1362", "google-drive": "\U000F02B6", "google-earth": "\U000F02B7", "google-fit": "\U000F096C", "google-glass": "\U000F02B8", "google-hangouts": "\U000F02C9", - "google-home": "\U000F0824", "google-keep": "\U000F06DC", "google-lens": "\U000F09F6", "google-maps": "\U000F05F5", @@ -3582,6 +3624,7 @@ md_icons = { "hand-clap-off": "\U000F1A42", "hand-coin": "\U000F188F", "hand-coin-outline": "\U000F1890", + "hand-cycle": "\U000F1B9C", "hand-extended": "\U000F18B6", "hand-extended-outline": "\U000F18B7", "hand-front-left": "\U000F182B", @@ -3615,6 +3658,7 @@ md_icons = { "harddisk-remove": "\U000F104C", "hat-fedora": "\U000F0BA4", "hazard-lights": "\U000F0C89", + "hdmi-port": "\U000F1BB8", "hdr": "\U000F0D7D", "hdr-off": "\U000F0D7E", "head": "\U000F135E", @@ -3768,6 +3812,8 @@ md_icons = { "home-roof": "\U000F112B", "home-search": "\U000F13B0", "home-search-outline": "\U000F13B1", + "home-silo": "\U000F1BA0", + "home-silo-outline": "\U000F1BA1", "home-switch": "\U000F1794", "home-switch-outline": "\U000F1795", "home-thermometer": "\U000F0F54", @@ -3827,6 +3873,7 @@ md_icons = { "human-pregnant": "\U000F05CF", "human-queue": "\U000F1571", "human-scooter": "\U000F11E9", + "human-walker": "\U000F1B71", "human-wheelchair": "\U000F138D", "human-white-cane": "\U000F1981", "humble-bundle": "\U000F0744", @@ -4005,6 +4052,7 @@ md_icons = { "klingon": "\U000F135B", "knife": "\U000F09FB", "knife-military": "\U000F09FC", + "knob": "\U000F1B96", "koala": "\U000F173F", "kodi": "\U000F0314", "kubernetes": "\U000F10FE", @@ -4092,7 +4140,7 @@ md_icons = { "leaf-off": "\U000F12D9", "leak": "\U000F0DD7", "leak-off": "\U000F0DD8", - "lecturn": "\U000F1AF0", + "lectern": "\U000F1AF0", "led-off": "\U000F032B", "led-on": "\U000F032C", "led-outline": "\U000F032D", @@ -4182,6 +4230,8 @@ md_icons = { "lipstick": "\U000F13B5", "liquid-spot": "\U000F1826", "liquor": "\U000F191E", + "list-box": "\U000F1B7B", + "list-box-outline": "\U000F1B7C", "list-status": "\U000F15AB", "litecoin": "\U000F0A61", "loading": "\U000F0772", @@ -4362,6 +4412,8 @@ md_icons = { "message-bookmark-outline": "\U000F15AD", "message-bulleted": "\U000F06A2", "message-bulleted-off": "\U000F06A3", + "message-check": "\U000F1B8A", + "message-check-outline": "\U000F1B8B", "message-cog": "\U000F06F1", "message-cog-outline": "\U000F1172", "message-draw": "\U000F0363", @@ -4410,6 +4462,8 @@ md_icons = { "metronome-tick": "\U000F07DB", "micro-sd": "\U000F07DC", "microphone": "\U000F036C", + "microphone-message": "\U000F050A", + "microphone-message-off": "\U000F050B", "microphone-minus": "\U000F08B3", "microphone-off": "\U000F036D", "microphone-outline": "\U000F036E", @@ -4519,7 +4573,8 @@ md_icons = { "more": "\U000F037B", "mortar-pestle": "\U000F1748", "mortar-pestle-plus": "\U000F03F1", - "mosque": "\U000F1827", + "mosque": "\U000F0D45", + "mosque-outline": "\U000F1827", "mother-heart": "\U000F1314", "mother-nurse": "\U000F0D21", "motion": "\U000F15B2", @@ -4594,8 +4649,11 @@ md_icons = { "movie-star-outline": "\U000F1718", "mower": "\U000F166F", "mower-bag": "\U000F1670", + "mower-bag-on": "\U000F1B60", + "mower-on": "\U000F1B5F", "muffin": "\U000F098C", "multicast": "\U000F1893", + "multimedia": "\U000F1B97", "multiplication": "\U000F0382", "multiplication-box": "\U000F0383", "mushroom": "\U000F07DF", @@ -4624,6 +4682,7 @@ md_icons = { "music-note-eighth-dotted": "\U000F0F71", "music-note-half": "\U000F0389", "music-note-half-dotted": "\U000F0F72", + "music-note-minus": "\U000F1B89", "music-note-off": "\U000F038A", "music-note-off-outline": "\U000F0F73", "music-note-outline": "\U000F0F74", @@ -4846,7 +4905,13 @@ md_icons = { "office-building-cog-outline": "\U000F194A", "office-building-marker": "\U000F1520", "office-building-marker-outline": "\U000F1521", + "office-building-minus": "\U000F1BAA", + "office-building-minus-outline": "\U000F1BAB", "office-building-outline": "\U000F151F", + "office-building-plus": "\U000F1BA8", + "office-building-plus-outline": "\U000F1BA9", + "office-building-remove": "\U000F1BAC", + "office-building-remove-outline": "\U000F1BAD", "oil": "\U000F03C7", "oil-lamp": "\U000F0F19", "oil-level": "\U000F1053", @@ -4967,6 +5032,8 @@ md_icons = { "patio-heater": "\U000F0F80", "patreon": "\U000F0882", "pause": "\U000F03E4", + "pause-box": "\U000F00BC", + "pause-box-outline": "\U000F1B7A", "pause-circle": "\U000F03E5", "pause-circle-outline": "\U000F03E6", "pause-octagon": "\U000F03E7", @@ -5253,6 +5320,7 @@ md_icons = { "progress-clock": "\U000F0996", "progress-close": "\U000F110A", "progress-download": "\U000F0997", + "progress-helper": "\U000F1BA2", "progress-pencil": "\U000F1787", "progress-question": "\U000F1522", "progress-star": "\U000F1788", @@ -5351,12 +5419,14 @@ md_icons = { "razor-single-edge": "\U000F1998", "react": "\U000F0708", "read": "\U000F0447", - "receipt": "\U000F0449", - "receipt-outline": "\U000F19DC", + "receipt": "\U000F0824", + "receipt-outline": "\U000F04F7", + "receipt-text": "\U000F0449", "receipt-text-check": "\U000F1A63", "receipt-text-check-outline": "\U000F1A64", "receipt-text-minus": "\U000F1A65", "receipt-text-minus-outline": "\U000F1A66", + "receipt-text-outline": "\U000F19DC", "receipt-text-plus": "\U000F1A67", "receipt-text-plus-outline": "\U000F1A68", "receipt-text-remove": "\U000F1A69", @@ -5495,7 +5565,9 @@ md_icons = { "robot-off-outline": "\U000F167B", "robot-outline": "\U000F167A", "robot-vacuum": "\U000F070D", + "robot-vacuum-alert": "\U000F1B5D", "robot-vacuum-variant": "\U000F0908", + "robot-vacuum-variant-alert": "\U000F1B5E", "rocket": "\U000F0463", "rocket-launch": "\U000F14DE", "rocket-launch-outline": "\U000F14DF", @@ -5633,6 +5705,8 @@ md_icons = { "segment": "\U000F0ECB", "select": "\U000F0485", "select-all": "\U000F0486", + "select-arrow-down": "\U000F1B59", + "select-arrow-up": "\U000F1B58", "select-color": "\U000F0D31", "select-compare": "\U000F0AD9", "select-drag": "\U000F0A6C", @@ -5803,6 +5877,7 @@ md_icons = { "sign-pole": "\U000F14F8", "sign-real-estate": "\U000F1118", "sign-text": "\U000F0782", + "sign-yield": "\U000F1BAF", "signal": "\U000F04A2", "signal-2g": "\U000F0712", "signal-3g": "\U000F0713", @@ -5821,7 +5896,8 @@ md_icons = { "signature-freehand": "\U000F0DFC", "signature-image": "\U000F0DFD", "signature-text": "\U000F0DFE", - "silo": "\U000F0B49", + "silo": "\U000F1B9F", + "silo-outline": "\U000F0B49", "silverware": "\U000F04A3", "silverware-clean": "\U000F0FDE", "silverware-fork": "\U000F04A4", @@ -6000,6 +6076,9 @@ md_icons = { "speaker-message": "\U000F1B11", "speaker-multiple": "\U000F0D38", "speaker-off": "\U000F04C4", + "speaker-pause": "\U000F1B73", + "speaker-play": "\U000F1B72", + "speaker-stop": "\U000F1B74", "speaker-wireless": "\U000F071F", "spear": "\U000F1845", "speedometer": "\U000F04C5", @@ -6149,7 +6228,17 @@ md_icons = { "store-settings": "\U000F18D4", "store-settings-outline": "\U000F18D5", "storefront": "\U000F07C7", + "storefront-check": "\U000F1B7D", + "storefront-check-outline": "\U000F1B7E", + "storefront-edit": "\U000F1B7F", + "storefront-edit-outline": "\U000F1B80", + "storefront-minus": "\U000F1B83", + "storefront-minus-outline": "\U000F1B84", "storefront-outline": "\U000F10C1", + "storefront-plus": "\U000F1B81", + "storefront-plus-outline": "\U000F1B82", + "storefront-remove": "\U000F1B85", + "storefront-remove-outline": "\U000F1B86", "stove": "\U000F04DE", "strategy": "\U000F11D6", "stretch-to-page": "\U000F0F2B", @@ -6239,6 +6328,7 @@ md_icons = { "table-edit": "\U000F04F0", "table-eye": "\U000F1094", "table-eye-off": "\U000F13C3", + "table-filter": "\U000F1B8C", "table-furniture": "\U000F05BC", "table-headers-eye": "\U000F121D", "table-headers-eye-off": "\U000F121E", @@ -6272,7 +6362,6 @@ md_icons = { "table-sync": "\U000F13A1", "table-tennis": "\U000F0E68", "tablet": "\U000F04F6", - "tablet-android": "\U000F04F7", "tablet-cellphone": "\U000F09A7", "tablet-dashboard": "\U000F0ECE", "taco": "\U000F0762", @@ -6377,8 +6466,6 @@ md_icons = { "text-search-variant": "\U000F1A7E", "text-shadow": "\U000F0669", "text-short": "\U000F09A9", - "text-to-speech": "\U000F050A", - "text-to-speech-off": "\U000F050B", "texture": "\U000F050C", "texture-box": "\U000F0FE6", "theater": "\U000F050D", @@ -6429,13 +6516,13 @@ md_icons = { "timeline-check-outline": "\U000F1533", "timeline-clock": "\U000F11FB", "timeline-clock-outline": "\U000F11FC", - "timeline-help": "\U000F0F99", - "timeline-help-outline": "\U000F0F9A", "timeline-minus": "\U000F1534", "timeline-minus-outline": "\U000F1535", "timeline-outline": "\U000F0BD2", "timeline-plus": "\U000F0F96", "timeline-plus-outline": "\U000F0F97", + "timeline-question": "\U000F0F99", + "timeline-question-outline": "\U000F0F9A", "timeline-remove": "\U000F1536", "timeline-remove-outline": "\U000F1537", "timeline-text": "\U000F0BD3", @@ -6601,6 +6688,7 @@ md_icons = { "transition-masked": "\U000F0916", "translate": "\U000F05CA", "translate-off": "\U000F0E06", + "translate-variant": "\U000F1B99", "transmission-tower": "\U000F0D3E", "transmission-tower-export": "\U000F192C", "transmission-tower-import": "\U000F192D", @@ -6744,7 +6832,11 @@ md_icons = { "vector-intersection": "\U000F055D", "vector-line": "\U000F055E", "vector-link": "\U000F0FE8", - "vector-point": "\U000F055F", + "vector-point": "\U000F01C4", + "vector-point-edit": "\U000F09E8", + "vector-point-minus": "\U000F1B78", + "vector-point-plus": "\U000F1B79", + "vector-point-select": "\U000F055F", "vector-polygon": "\U000F0560", "vector-polygon-variant": "\U000F1856", "vector-polyline": "\U000F0561", @@ -6912,6 +7004,7 @@ md_icons = { "water-alert-outline": "\U000F1503", "water-boiler": "\U000F0F92", "water-boiler-alert": "\U000F11B3", + "water-boiler-auto": "\U000F1B98", "water-boiler-off": "\U000F11B4", "water-check": "\U000F1504", "water-check-outline": "\U000F1505", @@ -6951,6 +7044,7 @@ md_icons = { "weather-cloudy-alert": "\U000F0F2F", "weather-cloudy-arrow-right": "\U000F0E6E", "weather-cloudy-clock": "\U000F18F6", + "weather-dust": "\U000F1B5A", "weather-fog": "\U000F0591", "weather-hail": "\U000F0592", "weather-hazy": "\U000F0F30", @@ -7072,6 +7166,7 @@ md_icons = { "window-restore": "\U000F05B2", "window-shutter": "\U000F111C", "window-shutter-alert": "\U000F111D", + "window-shutter-auto": "\U000F1BA3", "window-shutter-cog": "\U000F1A8A", "window-shutter-open": "\U000F111E", "window-shutter-settings": "\U000F1A8B", @@ -7084,7 +7179,12 @@ md_icons = { "wrap": "\U000F05B6", "wrap-disabled": "\U000F0BDF", "wrench": "\U000F05B7", + "wrench-check": "\U000F1B8F", + "wrench-check-outline": "\U000F1B90", "wrench-clock": "\U000F19A3", + "wrench-clock-outline": "\U000F1B93", + "wrench-cog": "\U000F1B91", + "wrench-cog-outline": "\U000F1B92", "wrench-outline": "\U000F0BE0", "xamarin": "\U000F0845", "xml": "\U000F05C0", diff --git a/sbapp/kivymd/images/firebase-logo.png b/sbapp/kivymd/images/firebase-logo.png deleted file mode 100644 index 68674e64251d8657749159ccafde69b1cf051d45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5436 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 TGlJZe{}bTq?18U77JB7>kpHcX literal 29440 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 diff --git a/sbapp/kivymd/images/logo/kivymd-icon-128.png b/sbapp/kivymd/images/logo/kivymd-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..289c00122b00581389de4c931a3a454961ab1d32 GIT binary patch literal 19826 zcmeFZbyOT*vo<<2xDW0yxCD214;Cc2ySux)Yj6z`2*HC(AV6?;hu}_dx#ai0=X~d^ z`~7p)y7#{`+Ov1nQ&rEdy;t{ibw?>HN~0hWAOZjY6j>PwmACJ}zi)Whx6dC!M#BIA zq{3TG(^bXD1LElHU~Xk=266RrG=rFVTA2d?o=er)R_=tn>0z%X*d5R#Kb%?erup}G zZ#~elI2Xp8)thszWU=ri+BZ?r3Kn0l)(4(&11gaw`_!!Md39d;aI(*QRZp%gRswS4 zZr308wl$ufPMoe@!uATg=g*%Oo{la?w|&p_g`4M*RtBzwnXWJ5j(jtO0wy1iE-s!g zE?Ns}gqg3kMIFcam;ZS89EtUGeZ=|GVKmcj!EQ$jfcvRpVMf?q(EI!>u^k zcS6ti^jaWX`|&WP<}*Y8xKWGPm-9Ub`{7G!i=!cfx|^4WV*9oA7GZ%G`VfyfYyW3* zU!^ajXEj}ZE2RlYeoy4>>jME_cJBT691dMNe?)uD>hjy- zJ*3*R^&5zG^Kygw@-6=Q2uX>8;jfk9hNQAKJy*-!U@Ybm6UUSKel}l zl#;x!pZ9p$pH=|bRyAgfQxNjPv6%BGZ0xfb7wEd=g0U&TNdH7&MzR6J)Bu=msF2^& zL0@&nuTts73r`KkS{#uV%UOIoxe5>SkE(HLxHlr*=Tv9>3;A_#Kv7ocCsPX|9Ey;3?ucE@_M7&86~F z$79(?1%|8YM?0SoV_*%u($qC{{Mr=gPQFi%wH>!EpGK(gZ}z1q3iaoBFCCm!bxb`B zuLn@roThOGu$R)Ld(3$@q$a%Q>5)|#8_Z0I55=vM`blfMJMs~?SmSpp`(5N$nLJ5q zDhpn-Smd84zI>;-Z5qFkKRg8tWz+rnJ^E_EZjsvBHL_CU^{6+Ou4%j$D`;0=b^9o^#01ErhepcfRk9-gj}tn!1M5 zezTJag4Cm*U>j5D5Kf`fQm9!+f1DL@n9EIaM`x7LOnLs?`stocXnkqqS=PiqTLP;j zDQ0X1@)J*ap)o+2ox(KRQeCFXMHk#{xjhd;Y=YaWqWQ2F_o`CTpD5HpWgp*OskskU z^2xl%!dGJUNzMvcq73!Pk-?9Zxzo-U3?VX`<~%>hi+R-9`)q+`zx+o&CY7k%RazuL zCyu;eYtGfrj!&4+{(Eb~y!#IizJ}>J*9%>%Z-XC$!WGanQN8ao=c^=;y*`Nxn|&21 z9`66Tc~!}{Uyr3oc}E6d13 zA3P_-=`sJYByf&pC@;0V&b{33G*3_cg>!nDeleageXORZQ@y z8IgtlpZ5APj-DJZXRTGZJkQ|PnaXn?t<)1a#&AS8t_%_Q5}r%(`f^~#soL#;igtYQhpVz*V6)pQBHp$yBs?VGsd=A_W{bYmUKaN7Pex)yz zK3Vhtr~Ii#TS4tUzjDe~2uyg{J$M~6-pvse$$~0nb{)?hHNvmMawHZ(z_Klr$^N2%)U0Z zcIx*Tzg|@|d@bV=7vRgALXuvZvP=#9VsQ>Bokc+*a6vHbMz@9=2leDGHN=0zUE@-2 zH~ga9h{PX&=3$PVyk(^Xj}NPVsMyhh*6t>|4R!mQ>0*9A^tWz8SeqCLQZ~Fu!hk)p zxuQRo907jq*lDo>pb*yZeJ-jX${<$gP}s*kb1(F6UwK9VpK=f4N+9lx56inResYI* zo#Xs}l4e54)IWqGpn3v?mT!t@5I;~l4f`44t$@a8w>;0W_bCR9f99dprS8Za zqJ3ix5GxG$!-+#%L1t9$ovli_fD^kq`vnTDy|S%E7r&Y(Pv>Z&wSHi=0rN!c$fJvI z^`}x~L5Ci;L(V@?Jqs(B)8rUix2Qt|9vS;<6h)T@pT{7v zFKWPuD&cV=wQPz8g}YB}~zp&M|N#t{1D3D>hfZwHICs~*vu1tn_RFInyD z_7P~aL(bu1q`9|PqI3K5^e5CF9OnS@Z)lrL|eXM@p66RFDmpntR(Y;Zm0%B@V+U(T$`DI`bw;7)x&B{3(`* zmqa1kf3F0urrkc_SO!u4vK>H3`JHWO9Ih74mxrazz5!w;_An5qB)AzItg7kt?DWB! zx>6OB!8=`lmm55UM1=ea(s2t~kFs13f&huN#EUSq&(nmUioT(>D@L;t6(vT6~47wXuAbU;vf^tb;|4SjNAa3*=5l_Ui4`KEHES(%PF0Bz@nm*jt_#ToL%D3RxmstK>`S)i!} z(k9vs`K1IQzYNu3BGG}1Ex;*+HhoT{L}DqRnh!+zrWm9$U3Wxpb=w=IrnbLM`i_0u zBS?P7U^R+R%Gdl?;&0_32yr?myg!S*AD8NbIJ+$I^Co#~_`N&2U}Iqy0xk*E#x@st zcZE%eA}!#M@T%G=%ZC7N`|&h;6MUH1);EQEl@6ScARJ~dIN9GzkFdJcI1-=`=O)V~h#(c%1~UR;T^2fBfra8YN6DioeE4kFfZVmW7DJs0od-sO z7{RcZ?nQ6)nn>y8c!L$kGH)8eaBw~wY4zdRSSMjZP%g z0&STn{LG766lXR2ZR!gb_(K2=GD;_|6*W$+0@8$F`3_i~)(*y0J$A-g0c-YXje_?) z?ly)d(*2pZ;E=>ksyb5l!Lf~Heq{m@$*77yOj**@Smc0fOK9BCC!E}v!BSE(8`}g+ z(mi&{?hlQ3u!o9_eGk=@cn`Gy3?^h0=5VS49I}UO`NSBcpVVw< z_k&Bw;W2w~wVsSMl{UNvH=(r+bHQ{zPEe7h+nQo$2YhPYhu|Jcyq(h^fhvFq)kqZr z99^M{-H|f=smRIFTnT<)eWn&I%!f5rSB_t@_^i>SKV*2n5$}$;1THOp7zmMH_B~`r zEf5Y`FSWnXa(1%Q$DJYGvS~ee40oKigOK|Tn;(CCSmNm0c>UawM$a=OZ z2+ZVqCW=dwCdV?wzaL~s3MEaZ{ZSr5_kH9L{elB?g1I>k!+23-n-&)it(-p7dY%IU zSoup1TuT)UBjR)vjlC+2^%%r!2-GHJKgE}}%Iv_B1&P;)lDH%3z3=7x5`69cj;+_T z;`#8c(p+6A0obG4(ifV_r zg<};#TQ;G#v^SGWvlfC8ZCAVKBnl~f_8o;SiD(Nw@tNrOMw+OXP{JQKF^P!2^}QQ~ zRq824dT&>Jw_Tp;9cf~Tm23LKxxK+;*2BpIAn7*@2lB1J}XHV&{`8 zn(yvZ4^M>U*%K@PubihKYTLtZb_tM~{K6yV=JLoCM%1>pIV zDe}RH)ktEy?FCnGVqN6rpOa&pL3{p8wb=%c8RiYAO}mTNzFS;qVjjW$EdLVzUgQdL zB0IL=i9g{J&!okr_lOw*5$SZxP7zv19NOP%RSKrsYcJO$epM=5@tlfSWm+_pv|!tD zz~prn9}$lF1%wk~epI*6)rJ-n?>^G9m6>ZuTu||L12$G(V3L@KoJiBW4;G5ga@X;y z68>=%^0oZAP043aMe{q<1x_dOX6f@P)X*3`l1N%2P$iaW*N+lwh>1i{Ge>`d60U$_ zHaHLCXlag4p$G>L-rlxk>+@G?#5}@q%mgyFUqv;`#6risbM4%W!xpF3HZ`QgUal~X(O5I^Ye5Z?pljr)h zn+VQH0DIjcwG+6@p@+idnq2lnU+9jXZ`$UIE%U6Cif0L%t;fkajGqV4$__ZC!K0ms zd5LEv31#?|WG0Cq;k%(4;drrxCiWRx0S^Igf36ZWIMp`QVtoxJx|4?eG(P{vq`PUx z5!$r4gJro;m{lP&%)b0a4rt!nb{0uOKp`RC=9k>r z@>Dkz6*gmUgMG;)a9m^TgcO;L_ zAjK0EQIDS==w+^gNyF*gzd_r>pagOzSf)|Q{=!}{1TC;Yx$Nr(hm-Is*v@yFs7?du z6y%MFTxpyFg?8_bWNl9F-5^|>sG z5!zYt5KGmVCa4j|q3^Y$+0b~s$pBPDD(wG<}ROs_Jb+F*XsT8-3KIUpNg0y*S)l`xTx*q3SMl5k+ zc|m?;=v#YL#fST%P)v>pSdoL3fmC-MzTQ8Ml;k{$AspA8ZSy-oq{7h$z9aR?pk4+( zeU&3ubRtm;!27LJ2VbQ^wZ2%1!d7@bhw*=oYbh-D*I48Ie@ra$x zS02P>GQH4KDP4l%IV;v0ByXKt7ppFPZXa2jL*;h~>@0c`rtNiRd%`F+_LY^05Rrit z;7l4|;>OHavZN&)ZqIM^LKAU^GTsJBN2rT<9LS2;^P*9G0do=_s161Wock?`?AM#y zwn;xklT6?t97=qc+zknRd@2;7`i|h2HYG|{xAIFp$we6lg<;siwA;;ArCebW-94Sy zPbABj@MSYCpSh}V)~+C?HhBlXG!}7p&hP{Da$szW=OZXa*ewe+7mmsgJd|~m*=g^Fg?scVB+&!oSB$L8gJ{qbiJV3;a2%H|` zg6jie=D7Tr4n5mjtwZbP?A~dp56$i!!Lp&b`w~Mx;E4+4`U?4e z!Y36(ookRKdpWT_C&Yqil|)AmS1M7lbA+R?X?+PImbCb0!SF<3aWjjFjLeqT8$4@r zbXN7k&58kxm*v$0D|nSw zrZ;I^{Y5g^dgz`B5LP5{=&i6=Az~{JC0rmhdwU%zq^AxEa}pt~&PxebtC(D!Y0V6N zX)^~7O2=vONiyCI)(W|;Ssu%;rn_{&HR-W);pDI>g!#27AuBFdd{9jlp#!6%AuHH{ z$?>_3^$6?E*U*t`q2r7$Yy?mbn`~&GbIYs{%k*8hB$r?do%1q-k{}e!ZV51Qx78t` zp4{2V$}Q#K%E7ioh@1QMW_?2 z@nc*s)r-}LQsPNl2XYk+4i9i@O3kx=({>X2o}A@V84hPSfsm|30BZ928D{Ar7X$he zHIdPW-BR{hAC{hT;pfbDMX$t0?nLcTsu;o2pWd;F0XOIqyRDS$Up_oL&GRMge-^+T zeOO`5!P1CHGQWt>vp=(Qn6;(jmL0M&K@K%tkD6tOY{Z|ih@pxcG^T)Y{^RsbE2zJO z;6pC7r>~0`J!P~J@vv!pB&f*f8U>B+aa?Qpx(i&0_HW~_A{*D(iP3NlN|2_|G$cN_ zAtOU(sI)AX6aWm8Nb(I98X~X&7f($WGSv(FEmHJkcRCJ}l$|3-B_T8u+X>b!jU2@o zsOqIyBt8q7EGgYk`vs><39?P330pHVw~a;!@YFd0eN!s*JXQnEA~>wVV5oR|R^!79 z$w0XWQ25(GM-csGGVwu15$gE0xd8=5MCdeX?l6cSCRd)@buDt~X;v$^`k<1T&k-u# zh7c^r!uMI^Krxr`p#BJ6e7;J5&Kp){TBRQ*lZnp(bvkk>9`!?Qdnv`_M?(Zr@1UC3 z_{t6T3j8MT3yf=b5l~BXwGq&Am!LfIq;SYp-a^4aQ%8#O3F!>}M*nPo8SN~xzm(B? zh}tH=+j=E@1{5>%0GC+K<4}q3)K|~PwB=Yq1+}OBy?j<03XPMF95T0(RXw9)pEA{R ze7O_v^Kpd5+_Y`gPHJvig8{~pn4sTq-&lm-Dr1X*cpR>Oo)VsCB9>yJPEdt%)x~RO z{&II;BI9F3b_L4CuE!s~%9etk7`9g%lO&=vl!<>BY1fnEpl)bVNQRz|9Wg(&jD#B` zrqBK^$DL>nO6`Y^HKR5zScAf4Lhp=ormZTSUh^-g#Z+@ne2#35>m!DOQXUXAly(hq zThf&}HjYORqtsHx4 zm&$k;S*AfOv#dfjhIWaRY&v>(+L~0B@M96}@o+>tKGF;7z&9g3P> zA(T%F=J`{rIhW>RYJode?uc;0P|%lpL@^s{ePZ$#Pn%4oq(^=ntFCy-a-UI-Mr>~k z4ti>xQ{H}h>3vRYD=mva*!Yy@v@CnJ8co4e9sAqZj{|t#Cq^FLlK{aD^#SQq3E&74qdi?1&zwm_g|AeR3QLJVG1*vlOT6wqu%Ur&Pw?#Mr(uK@Un zv)o32q7wLLZOvf{I~}*~(0CnDa(@I9RvWqfJi4H8`IbWa$#>=L%=7cmVQ|_PNZblu zp{V`hGIJjwx*cwS6&(o`qOnh9cawEm)p9at-8(IWAHy37iz+rXX4Do9ujAc zd~!=zyqiCsUX*xujq{C$X8q`PlOuBM@p9QKFo?!(Y-;>C0HO7HW^_TP8yD7NvqG3mh1#p6-BrOlxv9r=789DP5aU@UJ<=}U5 zd>L6-Im3k|f7qUQT@>8l*w3Q8Oj4`K@+9z&EfTYg)nL>UsQlddLVsLOP`q%fBUn+C zsIknTTl2Ysmc$js`5BVD<7lQcxWGTOU5oIW~sbEpC(&_Q&*?* z<$6vb`o@>^B!N3AHPD1Wg>0<27**V&8t3aNpc4j0yG&Lisqmc61RkH68;#+tAEw*M zmN`Jcj_%d<_xy?$?rL;!->4S9rtect~y$O}T zVd(vi%rE2O50-XptdNz0{3mOVJoE}%1j<2LpEXVrb_O&qJfRzd^GA6?#zj4(GqXbMw}`@I8Ij=`ol7iadU3mi z5rH1s-zY4;^SefPL&UKND4I%fLK;l`;Abfkh5^j`^9x!EGRj$$SpT`Goa`$ijgXI$ z$RvpY+Ty1!{;&wysfZ}oI=pgIzoi~Az#YNF?cIS9hQPWDAx0Z|4rMznPRwe~J#?;M zUFpHb^1?(bDzg<7f?wc@7T=ZVcHKrXLR;k8VKJ(T#nM&|->*YE5C%E=mQ|r@a?KS?WEZ(IQih9jhgRsS4ySP4 z>^Y$#1#+E}SqEacy8H1m!VM;C-8}3NKGaYi$oVZ*RwmTZ$n%(r*o?hr^6=@kjUq=u zC0woXqblL3)cBEznl568WK=W``~VAsT<;p(77W5ezCI3Pt0~@dp|L9p9Hd=QyN;4P zp=$Mkca9+q5z_U_p+-HVYPDg4ZWGEYRAO^hx7kq3#P851VZF?ezEUE^ z6yRgEc)|U7V9YArZDs#fyI}r*En2X0bT#=~v>+%iiWGR2U}Jw#SeS=Rool zKpBIsVksA*cgt?8Gq|c5Wy>qrPbz{8hgO|iU=v7JQ(8M!cy0Qvc3Pd1LL6;9%8dCY z6W4aTOZWJ#e1gd8txQ5wL7vaV!H&tu)WO({$o@pd*-^;T3f@wPGHH6<4oLKN`idjqgDb2Wl^+S%H> z@OcW7|AWi-w*9x6nH=(uh^vhtxu$|LMBKsI48p<0!NkHS>1pN8MlOU15pXs&=Tni8 z`ZvVeFF|rkS64?qW@Zl$4<-+GCI@E=W>#KaUS<|HW;Qm)Hwi`;FMC%bPeyweioX#5 z!jLd?F>!w5&&t6b@)y&{*ul+JkevLjAMzjm**PjG{3pD<%fDH8aEH)q>_!Z9^r zHnTOedlPkev&#D4OiIftDF3I%UkWU&>>U5mdL#S4NxE8@{~xmcn{R)6{t4&5I`XFe zpSb@``afd-NBB)jL4i-g!Nl#ad$JOOmcM#?HvW zZpO-J%*w&dXv)cDX2#26YRtpK{a>JD?Oj}r>`l!6LcM`AS-s(KbDMJUa2lC1vYE1S zGji~7nlSR1aj`L)81u53n3=J#7;_u_7YHS1tGBE)vi+}K{e?1pgJLr`;xIL3;bAmp zW9MMx;N&o3H0CrlW8^S4WnpDAWq)&r};k2?dvM^(Iw72-D<1gWS zqRO&@C{?f_H#mURg&BDsc&ce;b z!OHf3gg%-%yS%01Ur<&SCiZ^;{~Z^;w`ks&HTs*VZvg*jyamH2?rdh{>fo&A;9x6A z{?`M@U(J8=8zS(pQIWB7d6V$^oAUoodQ~&0fBpK`9I&v z^dCcSdjIM&u{5%`FngQe|IVoYXt(;mjFu@2o3Sw~yBQ;=DX%#rhY1%uBQG~63!~Xv z@SJRBZ@q7R{x`!e4(6^NM$TrU7H^ThMe~+H|3m|!{TG*X|6Sa}((Er$EN`>>Z6H}# z)mVA?Sa|r@+266S^RcjyGygkc=D+j$KSCB@{(q4o@Q=WMSqR?r{?+!jz`U(j%>TJy z{hPGEJpTXq_%|p2KU#Q${$C^iBYyvvuK%U$f5gE5NcexV>woF`A2IMh68_)p`u~hB z#Q$^1V`l%h8|3kJKy$o1S@w1?1Z(_WS_1I;_gTua~eUYxBvi2 zjI4yHn&;9@a)+nMhZaJ#mC87O7b@bA~7x~(gff$s6wN1K+9 zOiwf~3n3_gk4Xz0+Xf&AAn_|X<12zf_J=K#S*q+)4+hwe3O& z+~!SlEy)5>;cA~Z1UB*aSxr6p_$SctJY*G@mN4KWDv6(e<*TNyoC6N{3qm+HT41M@ za1phbX&)$*8SXybal|*~TJsZ#`*gQs+Q2jRDUYUpkTq zXhUrs=xLE>15gfW#9e7yTj^+AT=;&*gxc(f*Xjj_B>25ZF*EIb-XDzPYY(;8!n1+# zb~aGA&Vhc=$8YCimRab_ZhPj|>(^%svA&_4^)Bk))pw~jO9BG>HVmi?9xu=x!ZkFl zAQN&}`qL;BO^+SiPDkpm2vT<>I9V<96b$CF_P?#EgCTC-SN)* z*K3Ybc8Nesh>=XIKn^=kgCU4r4V)(NfKUzv}3;!uWSBFR<}C}D-z?}yJ`0==wSq3!7UXvl)?nqRUL9G8DO zh_cUfl`zdxS&~8PoFce6T(xg#=LDb-Ua+gn}@4e$MW3Jg)9~*0Mi+Kn%sP zEpN{xgaFwe;tl8LNaE21PPD9;zn^faGs8D{M>~Y-6fr&uJwT-Sh2SuX3hnRlzM*%n z>9)B=q!aj0$GsPglsSHnST zcQAC_r~7nOlH285mTP_y`z>WR8ZM}9y115Rf&K5libbX!;>Z~GyQcn10JOc;sR z2SUD^uS*O4Vq7(_hxT=|mfCL>k!IF;u6f27$s+|@Hb&>yoPVn3Za=HGTz~S2Q>*D* z1dg1)7%qpa&UGKr0A!Ttzn0qSrj-63xcKusj$2=cI)Q6#J;iM|G7f#Q~4j?f|Ldh(RLzod{0Xy8RMJp4OF15O>Hf=9WZ ze|GH|UbPWcGT50=xK_7Z>Ak92dY9n9SArmC?y(Dv9&6nSdbh=_mCXX+P-9Yu#jBn+ zVCJl~GAn>RT5z<;W*~#``K9hBRPfumZ=P0vX>LFV0kbkXz-wL!0F!0!`RZpYb<3ru zY0g{#=RH#0ysjh`3XGH^D~AvCopIo>YjvW?YW1&ideo5DIA5+kT644E)Dv9MuKW?z5Oaan|I;*>(37TipHSx zo6pVddr$z-t~c<**FPx}y+tO&Ww_d3-Kvu$x`*!~INq&*b3m!Krx zj0C<{7fE!ropkvw_pf_5g*!CSL9Dx(nm;>&qENw-+4!A|t@pp}0I~h;ftsn*iTEq- z%RZXy%frvlu+YONP#oe%t-mQC=;Z`~pQhVsTW>5Xq?l(2BwQF)ia9m!O!U<19C!l8YWT1j~hv6k{pY_7*A*2%- zaNXCzibaqxowKSxY6M^gRV0bmG;>%AK!dM{K@!{qXq33xH1y`)i3tThy^wH1ykioj ztqC;o-VLg#d-gI#d zhLmzyEip%~Cou4VA68z)5S!2h6@Z|pC-RrdE^N)Q4>n^b#MSR`McpThSerrxL!k(o zIQijWT&rTdBg2s!LSeFgz+&0l4VOt>{4nK~nf8l@8u_Zc9PXxyn}D|0!OIK_08aFt zXjEHJOsI_es0Bju4hL}K5fbtBiNTJi=pcf3&?RA!iVHXm=qQH^cy5yzQxhcVnwYa- z1WU^}f(cHsLHM29Zj{j@4dR50+>qzH7b`R(yx_T&T{LJ)j>$$BTv#Qd(OeH+l0ccV zF}r0zK=jRJoz zQHwzeiaTT@3K2q4pd3K`k+xp%(b1wgx`)K5pF>2TDfoeG0H%4UM5d96o6^hl z!UAB5UP;Oik@vWv=vDmCt*+qkm_Olhr;#E)BF5tEU{tVo7Cul_S~>+)ZywA=J|W?@ z^^w6JC{Ba)i70%o?(0oslesBHB(mdI2!{)jPlpUZM#2;VKGC8F{M^Dfr34;}QH6`t z+9%r2VFxRi$b0MS>S+G586 zB3Mc&Ly)2`>08vM#9?PdM1hRXTi;yC%A~rdt~mL>JFG5L9d9Ts#&&se#Z*)wDO3qu zASsw_C01PcDr?!{LFR12@o7Id(|!xOEx&3OC7AV z0ZG%~I~LN3I)=Ag5^~a6wuO&8uR7LOfv3k_M}yboMiIn|!ukS(bq3-Phl?6ajvkLi z`yRpM#2Wvq^2B6gw!3mR^hPvCrevQw)BE0xkUqY^~3G14PR5Ha;k6QVk_K7 z`zL;EyKCAG#C1xcBHKXe6j9E;Q`3FwWi=nXmY@QwaS*jv_FI@QAZfmCf$=!SmFw@O z%{3+71b$l!^b`SDG@u=e8O+qPBnWr6D^qA4w7b0LlM>MFYA3=ya3?w!XLvx9N9kJlZdopR84(RcwApp5nrjVZ+*>`euJoa|G+eP5a z9s%eODl$#&3yXksTJB63^Kw_B<3$(2R@eFpEX@jMfk0y_K~MLhscdeu$9F<|dJig^ z0lZ)$24Ilzmg?-5!PgsbHwK#^!i~9njH-UJ;BcF#{CCMWDY0kL(Brr4Ta; zd~T}30~ji4Rj{k?rWKl7DiD8fBfMLIvpKuU=S>UVX3R;Hq4R5eF$Viqom`%~0)yA@ zPy4B`x^kF|$5=}t>`sI7PshjrIiJ8M_CqZ8HUa+|-BJqFusKusr@b~Va-0gm4IlSd zCwtnKojGCLV6?M1vaI9$ymImpaJaaja_@O*L)-T|+VRUh*yJh*X2_nFZ1WQnz>&Rp zZ#-#FEe-M1#_3xp?Qa`Qe22<{$$!N>EAew{3A;5yww^gwguF zP@aZqX8dlc!K2tvjCk}}ba2cG zvH(6ct|Tb(rFg*(;PHW7?#s&OJldBBO$-u~JU9OW?%1|l_ffX^9~RTykx+q-S99ja ziz>c!Km9z|Q1j)sVuXo%zNl7uT7Abu{_3XUWsiZZVF{Zt4Fd>-cKJvZVNm_sy#Ehv zr`HAsTT?*LqOd-||9a(Df)awh_nvi_@bHwyJzPwVND6#(i zu^3p$@j1(j*H4~Cx2vVJ;m&2y^SboI@#8e+U1Jwd6!WVkK>Q*HIhT?8WnSNprDB?r@Vx2~&JJb010Bu^KX z4iWCa#o3Pbg07rv$9+@hhZK)v%Pc`Q9t~g6LY}vlmp#!4s#RZ_XP_=of!a@Eo=fEK z;R5fuFXA>EJ@+S5Iz=}Sp$t67equ&2&A|j)|7wHYXk=G)6o4V$=xEa^>pw$4JZ|U9 z=xKKn3cFXC014=Z@5l8FuPP|%0G37$4U zJpq%)%Y^{2j3T((zoCT>Z9a?LMSi!twM;VwKQ3eDl9z|WL^Apgcbo__lThZjeomBd zjjm1Vr+NuxcHGc(G{A;vMXKM!Jq_9FNE))}NxerjZ(-m|tus$v<}&RG&XAu27EVYH)9+_-LcEKxYO?$M+SYi%$3TZbMN` z_1X9^F=u!2Y>GHXCD*0!nW5wgfbl(ViQt-(O>4K5sPwvBSqA0$r!DjaB5k+I^Laf` zgWndMK_^q97RtPCXY5Y50)kf_2jm7P`Mce@RkC+h6V4QsU#{F3x8Z`2^|0Bj`1!Sk8M0NV+ zx~986;DPXgM0qotx&jG>$c|ikkInQ`=P^jA*UiY$(tW9h?QTnd81KLBxPwAiTvihK5aS4=YkaS9!h1@3?*@G; z)usa~Dv*PTP^(FWSel`_7SDo-or`P?(NiTrD? z0-C7o85YmGKLP9}z=*V*5&dHyg?_kz4Igw8XmsoF$R?xvd>MmNQ+fI&$&o{{VGPpQ zC?!B$O2k_#3-mJ#VAWHJnCU1jqE8?*7r+O`8W3^KpLp_f6fb(fUjw=kkG0>4Jdv}C zz{GGKGId@~-BehRCVmzs^SZF7{W*ze}*QOiH`aa_iME)8_{=DvUQ}X9GdjKVSRPlZHxFV@=w{=}t z9iRMc3$`KqFak5b+fN~{b0x^LxBjuN9(02xzr=U5gTAs~eKq!-LV7Qc&VBh>{^nF2 zJtk-^QJeFhb|a0=5VXJlKFfy7T*d}4bX=Q$L<{{a;WF>9Oze(VQ$|DEqG{sPY>;%P z#-LCo@`O*9pT%?AG4cubHA*Yb;>~}0k7rIGOPSa{x+Whcl!^I!i$rppuV3DyW<&&* zLJKSlU*LWlgDG~b>^{N0&!g{$E0aPse|XttChM54n!(dl-+i!oyOE4Qg`xX3J&O;t z=h*(@OLx{EK`h%fHBu!72t}sOTfMnZZ^O5{hQP<%ucv7(KGKu-4`w)_ze(zo9ZVCR zj|1Kf@bxf6XnJXh_Px1Qc*y?&6JT8@Y(N+Ys)zgLDm|XV#?NI>&EDQAL?(JVL_RC7 z{F7Q<&D^afdLhW%J0n?7BFV*BWv{xkjO4uTEhp9_snoTVFDlmF!*=I*iUco)spJs} zx~v?pG+;_u+WC=v3yN+^W46Hj9M)gMRq6pBY8gq(K$fP>k#N_3KDOP65GK z>&smyfCUZoZS=iqo(0)8Mwi0hYxDra9=Os+{&BKJ6iTk z%b5Q$O)aHw{t;Kx3*qvEuK_RZK9hC@!kx3 zqoQCCKfdAIL?3yv`}>dc%=HaA#tRhh-TW#geXZU~ z|L0$nejgW}iMP^d{fdd6O&yFfXeReu1!SfWdm5TyCjL>u#9SM!(1=ZMWJQ$lc|OIE znRe+X=RQL?>_A8%P0#=2x$1p|8(^G}=Y#W${|7@vs)|*YN{1Pgp?|K(VqcDWUaot% zBGEL5olHzs3v3qpS2XCkSYiQRh7+8qmoFFwUiiG;hy0(mm4q3pcUV7O3Z!y>PW|=p zVMhxcfCV`(F|IGx03HMAk@~GXF1HGZ_^*=JX3$GTN}6w^O~=9@i3tdvP8Ya5P0GJy zV&?u-XqF}>H6|R?xq?J@P7u%irIuu@XPM6 z7XE3w$?WhE3RfO>7jAwroDa?+=n!zSvXAdl*4C7At6?}}yK2iBDd6_qBCQTGfz$GS z9&rX&QKuQd7a4jx@$%JN(_T2nC!4!VHyyX>oh`l8pj%$rF^Dc6 ztb{90*Ox{k4-{P*8C6R7j_0LrSa!OwP!h#jBTNtEcL=uBi6!2e1qfTJnM$Xc4Nh#f~7Wx`YQEPBl&L%WZT-%P><7 z+GNU~hIl;+!uttIThYwJbNTiZPzB6x|DOP(0$lwcIjB765|kQdmZM-!DrSqAJtF4i zu=*7RKnV~6B(w-fi$REX5_)@}{i;po`rUVk=FYt^!yVOn1!bk6vhl(imoiGbDBtou zC&}rFSKX152b{4BeavzcN{uk92`H6hQWi720<`SHuQ&iodjt_Cp*4ZDRtT|1=w@56 zd*?2_W791nvSv5TNN1(f(3G8m$|UkJK~3T_JUhMea3RdH+)Hqnh z0hwmNVrFk83BO_iC<1~2V1Pt`NQ6P68A7yxM2pajn7O9sDr4>T-6Gm{4F#ebVFcHd zlTIo-o!jztK~FGOd}eq~irvfwl}VnEll^Zv69cDNdT}sMGOIXPB>@TR z)?G0G6ak^&6$&tfKq3mFCXm)df}$jhs1a@L&^tEwYLV8fDcIBnBh*fs-cCYqV%^kG zm$iKcYKsp9w<&2n|F!Kn92m%KrNkPyZ2ui02c8Q#jdmHbt~mU2!olqP$-(7eRCy< h-Dr*0XpJ_z{eSgE^~eKWYtR4y002ovPDHLkV1gS3dH?_b literal 0 HcmV?d00001 diff --git a/sbapp/kivymd/images/logo/kivymd-icon-256.png b/sbapp/kivymd/images/logo/kivymd-icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..869b7683aa84a55645dbbb488fa93cb37c4718bb GIT binary patch literal 31943 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 literal 0 HcmV?d00001 diff --git a/sbapp/kivymd/images/logo/kivymd-icon-512.png b/sbapp/kivymd/images/logo/kivymd-icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..82d666d001f8da23df6ea300928d799e64cbf7f1 GIT binary patch literal 54349 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 literal 0 HcmV?d00001 diff --git a/sbapp/kivymd/images/quad_shadow-0.png b/sbapp/kivymd/images/quad_shadow-0.png deleted file mode 100644 index f847665c42b733c2bfd706fb360aba2b43fea42d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31415 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 diff --git a/sbapp/kivymd/images/quad_shadow-2.png b/sbapp/kivymd/images/quad_shadow-2.png deleted file mode 100644 index 1949d5e7f2686fdea550b32c28751990ec05d964..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21912 zcmeFXcUaTS@;3}3MM|Ve6Oi5vy@k*rgborAsVXHDX@SrLM0zJeC`zvZktV%KM|uZA zn)D`J5D*mJc;Bbo=Y7umE7$ct|DEJY`R>kqc6N4VcV}lKALyu(5i=0u;NXy{t3hBm zIJhytKSTuBl0K;<4IG>sM?MBd?l2247KEFNjlCnB#r-(~&I0$gx52^jp505}^W@{bP}dinyYFU;i1Xk;%Zeg#sHq7Ey}G;hxOTcxt5OFtW$=UVYpsV@6L}a<$p;)T=+I9Hq6yT zp3%tBvbgG_QG_92O%+vlrKSkR%y7CJ-yWua3x9}t#OD>=a@492?{h)LIMQBH_iJ4D zVx{%w1jWj2(SZ?n?{8`I`ZD-Z`!xlHHK)gWK1D?K&5ysLbi_tom3!Ce4E0Fsa-CG| zc{1ag@p^5UpDWW(Mo=87gmQV9RNwO)ZKEVPaC|?Am))^xIq$8evt*DvYMH>L&nQHA zNc_RCJQVeOPI!T(rz>GJ;g%(JQC#i?)w*d;4cmHTc z>z_D%zn+-rD}_xD$?1m-%Czw$(|A?402+6L&&cxlAx#|Cj`^sMPYJ2}lORPf2^)4L zMkYUSNIK?_;AVb5Rk5{W95HBEShAar>4otR_tn80L`i%EV%lJ%kL_VUD^_}Q%5qg6 z7N<5!>^*!|Wa3c65dK86!t&Xt!TD4VTEVi%3-bw*2eD~Oh)KgVahKBXD0S&XX{eX` z){9l`+RAN1$vHm<(|c-JdSNKAqc?-oNuNH~aHMgec9zp=hG+ixW6y;h=cWUv1CpXz z>m*FEfN9I@S4nXV?kU~%>7ubg=jWP~a>hq@G zFc%t#t+=09p+N>p2%;lO&M=}~*!4&It|V*xcrt&kD1hH6|NG0O_r=C3ehvMTw0KUY z5X-yJ2USlmb2|4kntN3DUOQbZ;JW10jhb%ppV0V_G>2X>=Y-1NCVM%4jYrRw#{y!C zmc{yj&A&B%dxX2yZ{wGZ5(Weak>Z_w2;#19c-w}2mmC-ycpc4yhq6>{`TXm{L@msH zmFSgdGTY{B-!!*V8A6m>nxIL;mWu88eQ9B$VA33)V&YxAlMGr|*z(RD9Gu(P_E>i_ z($)f7xi|?}Si4xl1-zXQSW$3rWaPaO7FG^$cNR;yt-Z4>+d)$+8;iZQEZZY-Z6R%h zGThEy&BqO{@1tX2<>O!_Wz8lpM=awF#sWCO-7Q$WogAH!U~gHrKXAd=|G$d`*;xJ{ zad(hqGtz#*qU_=ZXAu(+6AN1WZCT8-4S3xK`$>a z0WVPj7dKl$VJRsoK_L-A5fOeY1wZn+v%7^izcZ5kH^iS95IEAx4XZzU7iX5=m==~U z9`3SiY}k61zk&bOu8KFd@NW(Le*Oa<>258kj(rfr_6JKKC?X^T;ujL(7m*VDD?heY zTl*i}&d9%M5vxx@ZwrK=uz--DlheO&Al+3w|JmNZXFWj}9O>fWW(8OAggd*l z|FuzsqX+V@ZF(T#zl;8m+tJ!a5G&LlE&r8ARbBhRKX`tt(bnDx@dw9m>c3K2Tm6F$ z;o;`^2aUCrAlwn|gcSsdMHc=SdUt!9e-6;U^yhcY{~-vrxqsmQ3-#ai^@l8f=oJic zvGVxcs5(TJ?YF#OYZohfYw#a`K@yf0mf~p*3&+1o^&83>3nc`S6p<7ax8N6-l(66z0||@rTS`hoR9KebX)MeR31cd%s^1#u;-NwbuNtR99!iq&#|DQDm_D*npcZ=Uz6P6H_ z5C(~fgM>ihQeq&He-atO-H=!-{|zcEBp~ue}9qMN&yi<^V4o37{`vIhjO1wlhpJdu{+I>87FK_% zA8FwUxBg=YuzLKn%F52d*%pqSZvWDiiwKxi(ze_U&>Mf z&MzfwDQ0OQCM5xvu=rPWq>GKamxUW#(H3h1*m1`C)*s`{!ta~054ykb|IJYUJNCbl{mHHDf_RSgWIK0VFX#WV`F}(BCxVu}72Fx= z@?VMmSCT)~@|VR3EAyXa*hLPzW(xje(fpgK{Pw2*i?6?#;Qz%Iu+aZ$iS=4;D1H@AM5&$y8c%h_+JtK$GZN1Qy1|+!)~}UcJt?j4XT%KFaso?DRS3@Y z?{{`{VFI?~7DCM!iGxEz_4|X1lbTM4EhKbT*H$51Cm|rbk5fE@d5?p`f};*mG{Byo zWjG|T*HsiysSK+zJl|u#Jg0O$-MzYg;%R7MS}B0s;Uc-jzY`{;X3a&ckkfk>`Bbba zZOG=%m;SqyJZxC#|Jkc81eAUQr!8_XLH@|~`3Bvu6vCSU#wXkN6sqKqj?BtrP3{Ce z%ZJ7+fEZaZTrvd`TRM)2wdZ7^b3edY`KQU(J6`_It;cU1mGX6}=n9`%ySD5Yc}OA44Cw51mJyeX|vz3l={d)aiU=*M3j@O%*+{wzY(B*PHvA zKe$DyoeQ~8LSOS17ax6X&w}8gt-NgtSJtk1*=mlW)aYM87H&6^7RO6H50n8M_XGAj9&|J*Ko1wUKrI{|vwVGq&ymEY} zhLnlGkFG`o52|eG%-(N_;0B(2L4k)|&84Ftca7Dc9rvpMwnC3f+3|N48-s-0j_=Oz zG)3j6bMipNqY+F<|3JMY$gEBSYt z@2tg=505$7-*K)A%|CUWjOrqZhdd)SY*!fcxxP72RyE#fDRvsOsm7E~(0<4LJs+FK zz@}8kbI|H%!S1XH0RKrp27+ zOt6CMMuX}rady%tYCX0nAZC|sO7Z8zv`{0__%U!?qxsa@k5oIwv}?S zQE=|&`26dW6eyU~sZf8Irljvt8CY0MtzLpsUE9EwHCGaHa)+Xs?3E^442IlEZOm3M z#~TyRBRjR`%ANa&?E(T3T$bX^i=1K8+?$R1to9*N>`;Q3S)gyn&BRD;F?B(c@Uh(sz-!^S%c?=EA6hZRp zbqpZiO4?jqLu6h+gz_L_%L{VS+A%G;ENYI9=!EBvgq~1YHM0{7xYgT2;%%tXgyhJ_ zMj$7e-urjfBlD_AceoOyl#jw9T3B;kf(gEavaC>6zvrOef)N23V@;=5L)FkoYFC2i znv5R{f}eB-vsvl!X`<$r6{tYqbq7l(v&gL-p{vEP@-*3cuB3?1?P`EEBHstZ)Z-P4 znJ$f=Z-m`mK9akMP6N1YeirTd`~m3U#6#yirak=jNs!(-6U8r1nHCbI=Two)Xkl{N zoM9<8eEy`&abO2GbR{!ljWc593FPF($^*j{>1Q^4ib-M*Oy1UosC_eKyP%@@l@f8q z?sqLQuJ{$?h9T)s1foU*bt^kTcD?kGAiK}*hi5U)Zn3wObNitv$UD#nGM$yC?9hXb zGnc%jW%MnMvUqJ{;*?Dau-aa`EEUf@8-Ex0oOirEbu7^HY=>7QW_He@n_}e=Yt=ak z#jhu97k4Rs$wBCgED$L~6HHsceAvxLKK)X$BZ`dIR9{y0z5P%zv67QQuYj7yi~}2_ zwQb8-ZNH4zDuca+5Ss7rildJcQ4nm$-w86`)KTF zgy?L*yv$Ux#m}UOD=!4|qL5-R)jKlLq_>!F24<+Jz4q`p*DD5;cp2DEQ(t=^aT>rj zmdv*DcI8Zuk3pJL1E%S8b7tVFkxN#JX?zNl@m}YFKT5-GdhNz&$@-fb#)t3B3)lPP zZ`vMZ#dp5faz-^isjfXzbdYh51bk7UpF;C057zEO-N<1nUnysTz1o?7^2&S}dyTwf zwzL&8Xp;!Y7vX&U;`b<;;XP47iO9Ne^4BxBcYepotCVepP;5*ucll+|5f3K_B&Fy$ zxY`|ryxysMoJZ{;eO0G!pdK@UV9$*l*5O1^mY*D;8eDQ!8T4Q(MeQC_fv4S%itr+1 zKl1+swgB9qur%Z0GJJu#`NsT~f|t4hn~Y&=EGPhDwYIR_s!;9VN}5&;=O*G;ufjK< zIJ*^raBsHzSML_|3p@viV!dkj(O*g6?ERDX>V(y#72TjKyI#~B-S)spg0e^&n#vm3 zJ$hr47?noy9R{gkO*CA(T`fIhlG#l3wtd+%m&2zugi{*9YoHYc@RZ^(Gjl=U&M?Vi zs!y7n(e6t3A0+@Imm|pfP91sPl^!Ui&qjKAUZDU( zsBKFY?>wX;Xi*Oh)|(Z8>p{qC^lTgopOVqJZL-vb$Jt2V;tgVocS<&V?9iA}U(!ge zeb3x;FxGzGNW8W~*f@;q-PA7cC+UDg{H7&h6x$|H*6OwZhu2H8`+BI%6VI3mquyFr zx*baTyFHoLGqs74H^@v|NK=*F{&!lw14C00I@KgTQ(!8e%Ik6S4VzZMsA{`cNQQ>* z5|GP-3}NuoQP6jHtqIESq5YQ_GR%6umlH!HPc9GdYBs%20lBd+l+nH$BSl-~*>@gQ zflx9=Q+EeZ>z$Ks7F0Kfv-{_LoZ_rvKhf=a&9~YeUayJj_3z~5^|r&@5(M3LnjA@* zC<}F1eouMw7Hz*&68vy@*yHP2$N= z(%Ty2jox2*9GgHVV^rjeXhz~>G*MXgij8BsRnprSlC1DtKrPjrGe zWno2Eal1#eKSGlHpUj4N>xK!xQ+}7|hAdXn

*M9o$_nNY&xp`?`=iTXXj*i`WHA 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 Xpv^y3gVgrO|5*wbx-Uc&lvMgRM07r? diff --git a/sbapp/kivymd/images/quad_shadow.atlas b/sbapp/kivymd/images/quad_shadow.atlas deleted file mode 100644 index 68e0aad..0000000 --- a/sbapp/kivymd/images/quad_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} \ No newline at end of file diff --git a/sbapp/kivymd/images/rec_shadow-0.png b/sbapp/kivymd/images/rec_shadow-0.png deleted file mode 100644 index f02b919a2d8a5fc16a59f984c46ec881b5d69cf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46593 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�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 Vs`!V~6-HfPW?}VP6KRr*>ly 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 diff --git a/sbapp/kivymd/images/rec_st_shadow-1.png b/sbapp/kivymd/images/rec_st_shadow-1.png deleted file mode 100644 index 759ee65285764c0619509c5b8cb27f972b2ab3fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32265 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 G;{P8x^pl$a diff --git a/sbapp/kivymd/images/rec_st_shadow.atlas b/sbapp/kivymd/images/rec_st_shadow.atlas deleted file mode 100644 index d4c24ab..0000000 --- a/sbapp/kivymd/images/rec_st_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}} \ No newline at end of file diff --git a/sbapp/kivymd/images/restdb-logo.png b/sbapp/kivymd/images/restdb-logo.png deleted file mode 100644 index 01adbccdda467cd4cb390d92f7b208cf663b9aeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15475 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 diff --git a/sbapp/kivymd/images/round_shadow-0.png b/sbapp/kivymd/images/round_shadow-0.png deleted file mode 100644 index 26d984051562b2baf7d39cb92639aae94d222bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39635 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 HcaHmiYza5P diff --git a/sbapp/kivymd/images/round_shadow-1.png b/sbapp/kivymd/images/round_shadow-1.png deleted file mode 100644 index d0f4c0fd4a6f9ac07d583e28e27e20a2495c392f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40767 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

(Ig{HG6T4&}_d3cF!08sm%T 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 diff --git a/sbapp/kivymd/images/round_shadow-2.png b/sbapp/kivymd/images/round_shadow-2.png deleted file mode 100644 index d5feef2c8a5a7bedd94cfce29af5cea57fab701f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26510 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 diff --git a/sbapp/kivymd/images/round_shadow.atlas b/sbapp/kivymd/images/round_shadow.atlas deleted file mode 100644 index f25016d..0000000 --- a/sbapp/kivymd/images/round_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} \ No newline at end of file diff --git a/sbapp/kivymd/tests/base_test.py b/sbapp/kivymd/tests/base_test.py new file mode 100644 index 0000000..9fa52ef --- /dev/null +++ b/sbapp/kivymd/tests/base_test.py @@ -0,0 +1,9 @@ +from kivy.tests.common import GraphicUnitTest + +from kivymd.app import MDApp + + +class BaseTest(GraphicUnitTest): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.app = MDApp() # NOQA diff --git a/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py b/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py index 801f5bf..3b81fdf 100644 --- a/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py +++ b/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py @@ -35,8 +35,15 @@ assert "Icons" in LabelBase._fonts.keys() # NOQA images = os.listdir(kivymd.images_path) print(images) +assert "logo" in images +assert "alpha_layer.png" in images +assert "black.png" in images +assert "blue.png" in images +assert "red.png" in images +assert "green.png" in images +assert "yellow.png" in images assert "folder.png" in images -assert "rec_shadow.atlas" in images +assert "transparent.png" in images """ ) pyi_main.run( diff --git a/sbapp/kivymd/tests/test_backdrop.py b/sbapp/kivymd/tests/test_backdrop.py new file mode 100644 index 0000000..85b1c85 --- /dev/null +++ b/sbapp/kivymd/tests/test_backdrop.py @@ -0,0 +1,24 @@ +from kivymd.tests.base_test import BaseTest + + +class BackdropTest(BaseTest): + def test_backdrop_raw_app(self): + from kivymd.uix.backdrop import MDBackdrop + from kivymd.uix.backdrop.backdrop import ( + MDBackdropBackLayer, + MDBackdropFrontLayer, + ) + from kivymd.uix.screen import MDScreen + from kivymd.uix.widget import MDWidget + + self.render( + MDScreen( + MDBackdrop( + MDBackdropBackLayer(MDWidget()), + MDBackdropFrontLayer(MDWidget()), + id="backdrop", + title="Example Backdrop", + header_text="Menu:", + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_bottom_navigation.py b/sbapp/kivymd/tests/test_bottom_navigation.py new file mode 100644 index 0000000..9b3467c --- /dev/null +++ b/sbapp/kivymd/tests/test_bottom_navigation.py @@ -0,0 +1,32 @@ +from kivymd.tests.base_test import BaseTest + + +class BottomNavigationTest(BaseTest): + def test_bottom_navigation_m3_style_raw_app(self): + from kivymd.uix.bottomnavigation import ( + MDBottomNavigation, + MDBottomNavigationItem, + ) + from kivymd.uix.screen import MDScreen + + self.app.theme_cls.material_style = "M3" + self.render( + MDScreen( + MDBottomNavigation( + MDBottomNavigationItem( + name="screen 1", + text="Mail", + icon="gmail", + ), + MDBottomNavigationItem( + name="screen 2", + text="Twitter", + icon="twitter", + badge_icon="numeric-10", + ), + panel_color="#eeeaea", + selected_color_background="#97ecf8", + text_color_active="red", + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_card.py b/sbapp/kivymd/tests/test_card.py new file mode 100644 index 0000000..81dd0dd --- /dev/null +++ b/sbapp/kivymd/tests/test_card.py @@ -0,0 +1,25 @@ +from kivymd.tests.base_test import BaseTest + + +class CardTest(BaseTest): + def test_card_m3_style_raw_app(self): + from kivymd.uix.behaviors import RoundedRectangularElevationBehavior + from kivymd.uix.card import MDCard + from kivymd.uix.screen import MDScreen + + class MD3Card(MDCard, RoundedRectangularElevationBehavior): + pass + + self.app.theme_cls.material_style = "M3" + self.render( + MDScreen( + MD3Card( + size_hint=(None, None), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + size=("200dp", "100dp"), + line_color=(0.2, 0.2, 0.2, 0.8), + style="elevated", + md_bg_color="lightblue", + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_chip.py b/sbapp/kivymd/tests/test_chip.py new file mode 100644 index 0000000..6f30f67 --- /dev/null +++ b/sbapp/kivymd/tests/test_chip.py @@ -0,0 +1,16 @@ +from kivymd.tests.base_test import BaseTest + + +class ChipTest(BaseTest): + def test_chip_raw_app(self): + from kivymd.uix.chip import MDChip + from kivymd.uix.screen import MDScreen + + self.render( + MDScreen( + MDChip( + text="Portland", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_create_project.py b/sbapp/kivymd/tests/test_create_project.py index 3122f1e..31265fa 100644 --- a/sbapp/kivymd/tests/test_create_project.py +++ b/sbapp/kivymd/tests/test_create_project.py @@ -1,14 +1,13 @@ def test_create_project(): import os - import sys os.system( - f"{sys.executable} -m kivymd.tools.patterns.create_project " + f"python3.10 -m kivymd.tools.patterns.create_project " f"MVC " f"{os.path.expanduser('~')} " f"TestProject " - f"{sys.executable} " - f"master " + f"python3.10 " + f"stable " f"--name_screen TestProjectScreen " f"--name_database restdb " f"--use_hotreload yes" diff --git a/sbapp/kivymd/tests/test_fitimage.py b/sbapp/kivymd/tests/test_fitimage.py new file mode 100644 index 0000000..de159de --- /dev/null +++ b/sbapp/kivymd/tests/test_fitimage.py @@ -0,0 +1,24 @@ +from kivymd.tests.base_test import BaseTest + + +class FitImageTest(BaseTest): + def test_fitimage_raw_app(self): + import os + + from kivymd import images_path + from kivymd.uix.fitimage import FitImage + from kivymd.uix.screen import MDScreen + + self.render( + MDScreen( + FitImage( + source=os.path.join( + images_path, "logo", "kivymd-icon-512.png" + ), + size_hint=(0.5, 0.5), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + radius=[36, 36, 0, 0], + mipmap=True, + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_imagelist.py b/sbapp/kivymd/tests/test_imagelist.py new file mode 100644 index 0000000..4ca989e --- /dev/null +++ b/sbapp/kivymd/tests/test_imagelist.py @@ -0,0 +1,39 @@ +from kivymd.tests.base_test import BaseTest + + +class ImageListTest(BaseTest): + def test_imagelist_raw_app(self): + import os + + from kivymd import images_path + from kivymd.uix.button import MDIconButton + from kivymd.uix.imagelist import MDSmartTile + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + self.render( + MDScreen( + MDSmartTile( + MDIconButton( + icon="heart-outline", + theme_icon_color="Custom", + icon_color="red", + pos_hint={"center_y": 0.5}, + ), + MDLabel( + text="Julia and Julie", + bold=True, + color="white", + ), + radius=24, + box_radius=[0, 0, 24, 24], + box_color="grey", + source=os.path.join( + images_path, "logo", "kivymd-icon-512.png" + ), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + size_hint=(None, None), + size=("320dp", "320dp"), + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_list.py b/sbapp/kivymd/tests/test_list.py new file mode 100644 index 0000000..da5a045 --- /dev/null +++ b/sbapp/kivymd/tests/test_list.py @@ -0,0 +1,67 @@ +from kivymd.tests.base_test import BaseTest + + +class ListTest(BaseTest): + def test_list_raw_app(self): + import os + + from kivymd import images_path + from kivymd.uix.list import ( + IconLeftWidget, + IconRightWidget, + ImageLeftWidget, + IRightBodyTouch, + MDList, + OneLineAvatarIconListItem, + OneLineAvatarListItem, + OneLineIconListItem, + OneLineListItem, + ThreeLineListItem, + TwoLineListItem, + ) + from kivymd.uix.screen import MDScreen + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.selectioncontrol import MDCheckbox + + class RightCheckbox(IRightBodyTouch, MDCheckbox): + pass + + self.render( + MDScreen( + MDScrollView( + MDList( + OneLineListItem(text="Text"), + TwoLineListItem( + text="Text", secondary_text="secondary text" + ), + ThreeLineListItem( + text="Text", + secondary_text="secondary text", + tertiary_text="tertiary text", + ), + OneLineAvatarListItem( + ImageLeftWidget( + source=os.path.join( + images_path, "logo", "kivymd-icon-512.png" + ) + ), + text="Text", + ), + OneLineIconListItem( + IconLeftWidget(icon="plus"), + text="Text", + ), + OneLineAvatarIconListItem( + IconLeftWidget(icon="plus"), + IconRightWidget(icon="minus"), + text="Text", + ), + OneLineAvatarIconListItem( + IconLeftWidget(icon="plus"), + RightCheckbox(), + text="Text", + ), + ) + ) + ) + ) diff --git a/sbapp/kivymd/tests/test_navigationdrawer.py b/sbapp/kivymd/tests/test_navigationdrawer.py new file mode 100644 index 0000000..87039c2 --- /dev/null +++ b/sbapp/kivymd/tests/test_navigationdrawer.py @@ -0,0 +1,94 @@ +from kivymd.tests.base_test import BaseTest + + +class NavigationDrawerTest(BaseTest): + def test_navigationdrawer_raw_app(self): + from kivymd.uix.navigationdrawer import ( + MDNavigationDrawer, + MDNavigationDrawerDivider, + MDNavigationDrawerHeader, + MDNavigationDrawerItem, + MDNavigationDrawerLabel, + MDNavigationDrawerMenu, + MDNavigationLayout, + ) + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager + from kivymd.uix.toolbar import MDTopAppBar + + class DrawerClickableItem(MDNavigationDrawerItem): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.focus_color = "#e7e4c0" + self.unfocus_color = "#f7f4e7" + self.text_color = "#4a4939" + self.icon_color = "#4a4939" + self.ripple_color = "#c5bdd2" + self.selected_color = "#0c6c4d" + + class DrawerLabelItem(MDNavigationDrawerItem): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.bg_color = "#f7f4e7" + self.text_color = "#4a4939" + self.icon_color = "#4a4939" + _no_ripple_effect = True # NOQA + + self.app.theme_cls.material_style = "M3" + self.render( + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDTopAppBar( + title="Navigation Drawer", + elevation=10, + pos_hint={"top": 1}, + md_bg_color="#e7e4c0", + specific_text_color="#4a4939", + left_action_items=[ + ["menu", lambda x: self.nav_drawer_open()] + ], + ) + ) + ), + MDNavigationDrawer( + MDNavigationDrawerMenu( + MDNavigationDrawerHeader( + title="Header title", + title_color="#4a4939", + text="Header text", + spacing="4dp", + padding=("12dp", 0, 0, "56dp"), + ), + MDNavigationDrawerLabel( + text="Mail", + ), + DrawerClickableItem( + icon="gmail", + right_text="+99", + text_right_color="#4a4939", + text="Inbox", + radius=24, + ), + DrawerClickableItem( + icon="send", + text="Outbox", + radius=24, + ), + MDNavigationDrawerDivider(), + MDNavigationDrawerLabel( + text="Labels", + ), + DrawerLabelItem( + icon="information-outline", + text="Label", + ), + DrawerLabelItem( + icon="information-outline", + text="Label", + ), + ), + id="nav_drawer", + ), + ) + ) diff --git a/sbapp/kivymd/tests/test_tab.py b/sbapp/kivymd/tests/test_tab.py new file mode 100644 index 0000000..1ad5920 --- /dev/null +++ b/sbapp/kivymd/tests/test_tab.py @@ -0,0 +1,14 @@ +from kivymd.tests.base_test import BaseTest + + +class TabTest(BaseTest): + def test_tab_raw_app(self): + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabs, MDTabsBase + + class Tab(MDFloatLayout, MDTabsBase): + pass + + tab = MDTabs() + tab.add_widget(Tab(title="Tab")) + self.render(tab) diff --git a/sbapp/kivymd/tests/test_textfield.py b/sbapp/kivymd/tests/test_textfield.py new file mode 100644 index 0000000..b5654e1 --- /dev/null +++ b/sbapp/kivymd/tests/test_textfield.py @@ -0,0 +1,72 @@ +# from kivy.clock import Clock +# from kivy.uix.textinput import TextInput + +from kivymd.tests.base_test import BaseTest + + +class TextFieldTest(BaseTest): + def test_textfield_raw_app(self): + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDFlatButton + from kivymd.uix.screen import MDScreen + from kivymd.uix.textfield import MDTextField + + # def set_text(): + # for widget in self.screen.ids.box.children: + # if issubclass(widget.__class__, TextInput): + # widget.text = "Input text" + + self.render( + MDScreen( + MDBoxLayout( + MDTextField( + hint_text="Label", + helper_text="Error massage", + mode="rectangle", + max_text_length=5, + ), + MDTextField( + icon_left="git", + hint_text="Label", + helper_text="Error massage", + mode="rectangle", + ), + MDTextField( + icon_left="git", + hint_text="Label", + helper_text="Error massage", + mode="fill", + ), + MDTextField( + hint_text="Label", + helper_text="Error massage", + mode="fill", + ), + MDTextField( + hint_text="Label", + helper_text="Error massage", + ), + MDTextField( + icon_left="git", + hint_text="Label", + helper_text="Error massage", + ), + MDTextField( + hint_text="Round mode", + mode="round", + max_text_length=15, + helper_text="Massage", + ), + MDFlatButton( + text="SET TEXT", + pos_hint={"center_x": 0.5}, + ), + id="box", + orientation="vertical", + spacing="20dp", + adaptive_height=True, + size_hint_x=0.8, + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) diff --git a/sbapp/kivymd/theming.py b/sbapp/kivymd/theming.py index 2a7a0c6..7bc4116 100755 --- a/sbapp/kivymd/theming.py +++ b/sbapp/kivymd/theming.py @@ -9,16 +9,18 @@ Themes/Theming Material App ------------ -The main class of your application, which in `Kivy` inherits from the App class, -in `KivyMD` must inherit from the `MDApp` class. The `MDApp` class has -properties that allow you to control application properties -such as :attr:`color/style/font` of interface elements and much more. +The main class of your application, which in `Kivy` inherits from the +:class:`~kivy.app.App` class, in `KivyMD` must inherit from the +:class:`~kivymd.app.MDApp` class. The :class:`~kivymd.app.MDApp` class has +properties that allow you to control application properties such as +:attr:`color/style/font` of interface elements and much more. Control material properties --------------------------- -The main application class inherited from the `MDApp` class has the :attr:`theme_cls` -attribute, with which you control the material properties of your application. +The main application class inherited from the :class:`~kivymd.app.MDApp` class +has the :attr:`~kivymd.app.MDApp.theme_cls` attribute, with which you control +the material properties of your application. Changing the theme colors ------------------------- @@ -36,142 +38,182 @@ the `colors `_ +.. note:: Your custom colors *must* use the names of the + `existing colors as defined in the palette `_ e.g. You can have `Blue` but you cannot have `NavyBlue`. -Add the custom theme to the MDApp as shown in the following snippet. +Add the custom theme to the :class:`~kivymd.app.MDApp` as shown in the +following snippet. -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import ObjectProperty + .. tab:: Imperative python style with KV - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons + .. code-block:: python - colors = { - "Teal": { - "50": "e4f8f9", - "100": "bdedf0", - "200": "97e2e8", - "300": "79d5de", - "400": "6dcbd6", - "500": "6ac2cf", - "600": "63b2bc", - "700": "5b9ca3", - "800": "54888c", - "900": "486363", - "A100": "bdedf0", - "A200": "97e2e8", - "A400": "6dcbd6", - "A700": "5b9ca3", - }, - "Blue": { - "50": "e3f3f8", - "100": "b9e1ee", - "200": "91cee3", - "300": "72bad6", - "400": "62acce", - "500": "589fc6", - "600": "5191b8", - "700": "487fa5", - "800": "426f91", - "900": "35506d", - "A100": "b9e1ee", - "A200": "91cee3", - "A400": "62acce", - "A700": "487fa5", - }, - "Red": { - "50": "FFEBEE", - "100": "FFCDD2", - "200": "EF9A9A", - "300": "E57373", - "400": "EF5350", - "500": "F44336", - "600": "E53935", - "700": "D32F2F", - "800": "C62828", - "900": "B71C1C", - "A100": "FF8A80", - "A200": "FF5252", - "A400": "FF1744", - "A700": "D50000", - }, - "Light": { - "StatusBar": "E0E0E0", - "AppBar": "F5F5F5", - "Background": "FAFAFA", - "CardsDialogs": "FFFFFF", - "FlatButtonDown": "cccccc", - }, - "Dark": { - "StatusBar": "000000", - "AppBar": "212121", - "Background": "303030", - "CardsDialogs": "424242", - "FlatButtonDown": "999999", - } - } + from kivy.lang import Builder + from kivy.properties import ObjectProperty + + from kivymd.app import MDApp + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase + from kivymd.icon_definitions import md_icons + + colors = { + "Teal": { + "200": "#212121", + "500": "#212121", + "700": "#212121", + }, + "Red": { + "200": "#C25554", + "500": "#C25554", + "700": "#C25554", + }, + "Light": { + "StatusBar": "E0E0E0", + "AppBar": "#202020", + "Background": "#2E3032", + "CardsDialogs": "#FFFFFF", + "FlatButtonDown": "#CCCCCC", + }, + } - KV = ''' - MDBoxLayout: - orientation: "vertical" + KV = ''' + MDBoxLayout: + orientation: "vertical" - MDTopAppBar: - title: "Example Tabs" + MDTopAppBar: + title: "Custom theme" - MDTabs: - id: tabs + MDTabs: + id: tabs - + - MDIconButton: - id: icon - icon: root.icon - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' + MDIconButton: + id: icon + icon: root.icon + icon_size: "48sp" + theme_icon_color: "Custom" + icon_color: "white" + pos_hint: {"center_x": .5, "center_y": .5} + ''' - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' - icon = ObjectProperty() + icon = ObjectProperty() - class Example(MDApp): - icons = list(md_icons.keys())[15:30] + class Example(MDApp): + icons = list(md_icons.keys())[15:30] - def build(self): - self.theme_cls.colors = colors - self.theme_cls.primary_palette = "Blue" - self.theme_cls.accent_palette = "Teal" - return Builder.load_string(KV) + def build(self): + self.theme_cls.colors = colors + self.theme_cls.primary_palette = "Teal" + self.theme_cls.accent_palette = "Red" + return Builder.load_string(KV) - def on_start(self): - for name_tab in self.icons: - tab = Tab(text="This is " + name_tab, icon=name_tab) - self.root.ids.tabs.add_widget(tab) + def on_start(self): + for name_tab in self.icons: + tab = Tab(title="This is " + name_tab, icon=name_tab) + self.root.ids.tabs.add_widget(tab) - Example().run() + Example().run() -This will change the theme colors to your custom defintion. In all other + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.properties import ObjectProperty + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.icon_definitions import md_icons + from kivymd.uix.toolbar import MDTopAppBar + + colors = { + "Teal": { + "200": "#212121", + "500": "#212121", + "700": "#212121", + }, + "Red": { + "200": "#C25554", + "500": "#C25554", + "700": "#C25554", + }, + "Light": { + "StatusBar": "E0E0E0", + "AppBar": "#202020", + "Background": "#2E3032", + "CardsDialogs": "#FFFFFF", + "FlatButtonDown": "#CCCCCC", + }, + } + + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + + icon = ObjectProperty() + + + class Example(MDApp): + icons = list(md_icons.keys())[15:30] + + def build(self): + self.theme_cls.colors = colors + self.theme_cls.primary_palette = "Teal" + self.theme_cls.accent_palette = "Red" + + return ( + MDBoxLayout( + MDTopAppBar(title="Custom theme"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + for name_tab in self.icons: + self.root.ids.tabs.add_widget( + Tab( + MDIconButton( + icon=name_tab, + icon_size="48sp", + theme_icon_color="Custom", + icon_color="white", + pos_hint={"center_x": .5, "center_y": .5}, + ), + title="This is " + name_tab, + icon=name_tab, + ) + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-color.png + :align: center + +This will change the theme colors to your custom definition. In all other respects, the theming stays as documented. .. warning:: Please note that the key ``'Red'`` is a required key for the - dictionary ``colors``. + dictionary :attr:`kivymd.color_definition.colors`. """ - +from kivy.animation import Animation from kivy.app import App -from kivy.atlas import Atlas from kivy.clock import Clock from kivy.core.window import Window from kivy.event import EventDispatcher @@ -181,13 +223,13 @@ from kivy.properties import ( BooleanProperty, ColorProperty, DictProperty, + NumericProperty, ObjectProperty, OptionProperty, StringProperty, ) from kivy.utils import get_color_from_hex -from kivymd import images_path from kivymd.color_definitions import colors, hue, palette from kivymd.font_definitions import theme_font_styles from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE @@ -207,29 +249,63 @@ class ThemeManager(EventDispatcher): To change the color scheme of an application: - .. code-block:: python + .. tabs:: - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton + .. tab:: Imperative python style with KV + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDRectangleFlatButton: + text: "Hello, World" + pos_hint: {"center_x": .5, "center_y": .5} + ''' - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" # "Purple", "Red" - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Red" # "Purple", "Red" + + return Builder.load_string(KV) - MainApp().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRectangleFlatButton + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" # "Purple", "Red" + + return ( + MDScreen( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png + :align: center :attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty` and defaults to `'Blue'`. @@ -244,36 +320,64 @@ class ThemeManager(EventDispatcher): To change the hue color scheme of an application: - .. code-block:: python + .. tabs:: - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton + .. tab:: Imperative python style with KV + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.button import MDRectangleFlatButton - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" # "Purple", "Red" - self.theme_cls.primary_hue = "200" # "500" - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen + class MainApp(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.primary_hue = "200" # "500" + screen = MDScreen() + screen.add_widget( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + return screen - MainApp().run() + MainApp().run() - With a value of ``self.theme_cls.primary_hue = "500"``: + .. tab:: Declarative python style - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png + .. code-block:: python - With a value of ``self.theme_cls.primary_hue = "200"``: + from kivymd.app import MDApp + from kivymd.uix.button import MDRectangleFlatButton + from kivymd.uix.screen import MDScreen - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-hue.png + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_hue = "200" # "500" + + return ( + MDScreen( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) + + + Example().run() + + With a value of ``self.theme_cls.primary_hue = "200"`` and ``self.theme_cls.primary_hue = "500"``: + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary_hue.png + :align: center :attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty` and defaults to `'500'`. @@ -304,7 +408,7 @@ class ThemeManager(EventDispatcher): _get_primary_color, bind=("primary_palette", "primary_hue") ) """ - The color of the current application theme in ``rgba`` format. + The color of the current application theme. :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that returns the value of the current application theme, property is readonly. @@ -319,42 +423,82 @@ class ThemeManager(EventDispatcher): _get_primary_light, bind=("primary_palette", "primary_light_hue") ) """ - Colors of the current application color theme in ``rgba`` format - (in lighter color). + Colors of the current application color theme (in lighter color). - .. code-block:: python + .. tabs:: - from kivy.lang import Builder + .. tab:: Declarative style with KV - from kivymd.app import MDApp + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp - KV = ''' - MDScreen: + KV = ''' + MDScreen: - MDRaisedButton: - text: "primary_light" - pos_hint: {"center_x": 0.5, "center_y": 0.7} - md_bg_color: app.theme_cls.primary_light + MDRaisedButton: + text: "primary_light" + pos_hint: {"center_x": 0.5, "center_y": 0.7} + md_bg_color: app.theme_cls.primary_light - MDRaisedButton: - text: "primary_color" - pos_hint: {"center_x": 0.5, "center_y": 0.5} + MDRaisedButton: + text: "primary_color" + pos_hint: {"center_x": 0.5, "center_y": 0.5} - MDRaisedButton: - text: "primary_dark" - pos_hint: {"center_x": 0.5, "center_y": 0.3} - md_bg_color: app.theme_cls.primary_dark - ''' + MDRaisedButton: + text: "primary_dark" + pos_hint: {"center_x": 0.5, "center_y": 0.3} + md_bg_color: app.theme_cls.primary_dark + ''' - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" - return Builder.load_string(KV) + class MainApp(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - MainApp().run() + MainApp().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRaisedButton + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" + + return ( + MDScreen( + MDRaisedButton( + text="Primary light", + pos_hint={"center_x": 0.5, "center_y": 0.7}, + md_bg_color=self.theme_cls.primary_light, + ), + MDRaisedButton( + text="Primary color", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), + MDRaisedButton( + text="Primary dark", + pos_hint={"center_x": 0.5, "center_y": 0.3}, + md_bg_color=self.theme_cls.primary_dark, + ), + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png :align: center @@ -373,8 +517,7 @@ class ThemeManager(EventDispatcher): _get_primary_dark, bind=("primary_palette", "primary_dark_hue") ) """ - Colors of the current application color theme - in ``rgba`` format (in darker color). + Colors of the current application color theme (in darker color). :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that returns the value of the current application theme (in darker color), @@ -384,12 +527,8 @@ class ThemeManager(EventDispatcher): accent_palette = OptionProperty("Amber", options=palette) """ The application color palette used for items such as the tab indicator - in the :attr:`MDTabsBar` class and so on... - - The image below shows the color schemes with the values - ``self.theme_cls.accent_palette = 'Blue'``, ``Red'`` and ``Yellow'``: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-palette.png + in the :class:`~kivymd.uix.tab.MDTabsBar` class and so on. + See :attr:`kivymd.uix.tab.MDTabsBar.indicator_color` attribute. :attr:`accent_palette` is an :class:`~kivy.properties.OptionProperty` and defaults to `'Amber'`. @@ -397,8 +536,7 @@ class ThemeManager(EventDispatcher): accent_hue = OptionProperty("500", options=hue) """ - Similar to :attr:`primary_hue`, - but returns a value for :attr:`accent_palette`. + Similar to :attr:`primary_hue`, but returns a value for :attr:`accent_palette`. :attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty` and defaults to `'500'`. @@ -429,12 +567,11 @@ class ThemeManager(EventDispatcher): _get_accent_color, bind=["accent_palette", "accent_hue"] ) """ - Similar to :attr:`primary_color`, but returns a value - for :attr:`accent_color`. + Similar to :attr:`primary_color`, but returns a value for :attr:`accent_color`. :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_color`, - property is readonly. + returns the value in ``rgba`` format for :attr:`accent_color`, property is + readonly. """ def _get_accent_light(self) -> list: @@ -446,12 +583,11 @@ class ThemeManager(EventDispatcher): _get_accent_light, bind=["accent_palette", "accent_light_hue"] ) """ - Similar to :attr:`primary_light`, but returns a value - for :attr:`accent_light`. + Similar to :attr:`primary_light`, but returns a value for :attr:`accent_light`. :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_light`, - property is readonly. + returns the value in ``rgba`` format for :attr:`accent_light`, property is + readonly. """ def _get_accent_dark(self) -> list: @@ -463,12 +599,11 @@ class ThemeManager(EventDispatcher): _get_accent_dark, bind=["accent_palette", "accent_dark_hue"] ) """ - Similar to :attr:`primary_dark`, but returns a value - for :attr:`accent_dark`. + Similar to :attr:`primary_dark`, but returns a value for :attr:`accent_dark`. :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_dark`, - property is readonly. + returns the value in ``rgba`` format for :attr:`accent_dark`, property is + readonly. """ material_style = OptionProperty("M2", options=["M2", "M3"]) @@ -488,33 +623,211 @@ class ThemeManager(EventDispatcher): and defaults to `'M2'`. """ + theme_style_switch_animation = BooleanProperty(False) + """ + Animate app colors when switching app color scheme ('Dark/light'). + + .. versionadded:: 1.1.0 + + .. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDCard: + orientation: "vertical" + padding: 0, 0, 0 , "36dp" + size_hint: .5, .5 + pos_hint: {"center_x": .5, "center_y": .5} + elevation: 4 + shadow_radius: 6 + shadow_offset: 0, 2 + + MDLabel: + text: "Theme style - {}".format(app.theme_cls.theme_style) + halign: "center" + valign: "center" + bold: True + font_style: "H5" + + MDRaisedButton: + text: "Set theme" + on_release: app.switch_theme_style() + pos_hint: {"center_x": .5} + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style_switch_animation = True + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + def switch_theme_style(self): + self.theme_cls.primary_palette = ( + "Orange" if self.theme_cls.primary_palette == "Red" else "Red" + ) + self.theme_cls.theme_style = ( + "Dark" if self.theme_cls.theme_style == "Light" else "Light" + ) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRaisedButton + from kivymd.uix.card import MDCard + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style_switch_animation = True + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDCard( + MDLabel( + id="label", + text="Theme style - {}".format(self.theme_cls.theme_style), + halign="center", + valign="center", + bold=True, + font_style="H5", + ), + MDRaisedButton( + text="Set theme", + on_release=self.switch_theme_style, + pos_hint={"center_x": 0.5}, + ), + id="card", + orientation="vertical", + padding=(0, 0, 0, "36dp"), + size_hint=(0.5, 0.5), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + elevation=4, + shadow_radius=6, + shadow_offset=(0, 2), + ) + ) + ) + + def switch_theme_style(self, *args): + self.theme_cls.primary_palette = ( + "Orange" if self.theme_cls.primary_palette == "Red" else "Red" + ) + self.theme_cls.theme_style = ( + "Dark" if self.theme_cls.theme_style == "Light" else "Light" + ) + self.root.ids.card.ids.label.text = ( + "Theme style - {}".format(self.theme_cls.theme_style) + ) + + + Example().run() + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation.gif + :align: center + + :attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + theme_style_switch_animation_duration = NumericProperty(0.2) + """ + Duration of the animation of switching the color scheme of the application + ("Dark/light"). + + .. versionadded:: 1.1.0 + + .. code-block:: python + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style_switch_animation = True + self.theme_cls.theme_style_switch_animation_duration = 0.8 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation-duration.gif + :align: center + + :attr:`theme_style_switch_animation_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + theme_style = OptionProperty("Light", options=["Light", "Dark"]) """ App theme style. - .. code-block:: python + .. tabs:: - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton + .. tab:: Imperative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.button import MDRectangleFlatButton - class MainApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen + class MainApp(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" # "Light" + screen = MDScreen() + screen.add_widget( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + return screen - MainApp().run() + MainApp().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRectangleFlatButton + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" # "Light" + + return ( + MDScreen( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png + :align: center :attr:`theme_style` is an :class:`~kivy.properties.OptionProperty` and defaults to `'Light'`. @@ -538,38 +851,76 @@ class ThemeManager(EventDispatcher): Similar to :attr:`bg_dark`, but the color values are a tone lower (darker) than :attr:`bg_dark`. - .. code-block:: python + .. tabs:: - KV = ''' - MDBoxLayout: + .. tab:: Declarative style with KV - MDBoxLayout: - md_bg_color: app.theme_cls.bg_light + .. code-block:: python - MDBoxLayout: - md_bg_color: app.theme_cls.bg_normal + from kivy.lang import Builder - MDBoxLayout: - md_bg_color: app.theme_cls.bg_dark + from kivymd.app import MDApp - MDBoxLayout: - md_bg_color: app.theme_cls.bg_darkest - ''' + KV = ''' + MDBoxLayout: - from kivy.lang import Builder + MDWidget: + md_bg_color: app.theme_cls.bg_light - from kivymd.app import MDApp + MDBoxLayout: + md_bg_color: app.theme_cls.bg_normal + + MDBoxLayout: + md_bg_color: app.theme_cls.bg_dark + + MDBoxLayout: + md_bg_color: app.theme_cls.bg_darkest + ''' - class MainApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - return Builder.load_string(KV) + class MainApp(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" # "Light" + return Builder.load_string(KV) - MainApp().run() + MainApp().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.widget import MDWidget + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" # "Light" + + return ( + MDBoxLayout( + MDWidget( + md_bg_color=self.theme_cls.bg_light, + ), + MDWidget( + md_bg_color=self.theme_cls.bg_normal, + ), + MDWidget( + md_bg_color=self.theme_cls.bg_dark, + ), + MDWidget( + md_bg_color=self.theme_cls.bg_darkest, + ), + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png + :align: center :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that returns the value in ``rgba`` format for :attr:`bg_darkest`, @@ -978,14 +1329,22 @@ class ThemeManager(EventDispatcher): ): self.set_clearcolor_by_theme_style(theme_style) - set_clearcolor = BooleanProperty(True) + _set_clearcolor = False def set_clearcolor_by_theme_style(self, theme_style): - if not self.set_clearcolor: - return - Window.clearcolor = get_color_from_hex( - self.colors[theme_style]["Background"] - ) + if self.theme_style_switch_animation and self._set_clearcolor: + Animation( + clearcolor=get_color_from_hex( + self.colors[theme_style]["Background"] + ), + d=self.theme_style_switch_animation_duration, + t="linear", + ).start(Window) + else: + Window.clearcolor = get_color_from_hex( + self.colors[theme_style]["Background"] + ) + self._set_clearcolor = True # Font name, size (sp), always caps, letter spacing (sp). font_styles = DictProperty( @@ -1009,45 +1368,93 @@ class ThemeManager(EventDispatcher): """ Data of default font styles. - Add custom font: + Add custom font + --------------- - .. code-block:: python + .. tabs:: - KV = ''' - MDScreen: + .. tab:: Declarative style with KV - MDLabel: - text: "JetBrainsMono" - halign: "center" - font_style: "JetBrainsMono" - ''' + .. code-block:: python - from kivy.core.text import LabelBase - from kivy.lang import Builder + from kivy.core.text import LabelBase + from kivy.lang import Builder - from kivymd.app import MDApp - from kivymd.font_definitions import theme_font_styles + from kivymd.app import MDApp + from kivymd.font_definitions import theme_font_styles + + KV = ''' + MDScreen: + + MDLabel: + text: "JetBrainsMono" + halign: "center" + font_style: "JetBrainsMono" + ''' - class MainApp(MDApp): - def build(self): - LabelBase.register( - name="JetBrainsMono", - fn_regular="JetBrainsMono-Regular.ttf") + class MainApp(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" - theme_font_styles.append('JetBrainsMono') - self.theme_cls.font_styles["JetBrainsMono"] = [ - "JetBrainsMono", - 16, - False, - 0.15, - ] - return Builder.load_string(KV) + LabelBase.register( + name="JetBrainsMono", + fn_regular="JetBrainsMono-Regular.ttf") + + theme_font_styles.append('JetBrainsMono') + self.theme_cls.font_styles["JetBrainsMono"] = [ + "JetBrainsMono", + 16, + False, + 0.15, + ] + return Builder.load_string(KV) - MainApp().run() + MainApp().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.core.text import LabelBase + + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.label import MDLabel + from kivymd.font_definitions import theme_font_styles + + + class MainApp(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + + LabelBase.register( + name="JetBrainsMono", + fn_regular="JetBrainsMono-Regular.ttf") + + theme_font_styles.append('JetBrainsMono') + self.theme_cls.font_styles["JetBrainsMono"] = [ + "JetBrainsMono", + 16, + False, + 0.15, + ] + return ( + MDScreen( + MDLabel( + text="JetBrainsMono", + halign="center", + font_style="JetBrainsMono", + ) + ) + ) + + + MainApp().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png + :align: center :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`. """ @@ -1080,31 +1487,59 @@ class ThemeManager(EventDispatcher): Note that all values *must* be provided. If you only want to set one or two values use the appropriate method call for that. - .. code-block:: python + .. tabs:: - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton + .. tab:: Imperative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.button import MDRectangleFlatButton + + class MainApp(MDApp): + def build(self): + self.theme_cls.set_colors( + "Blue", "600", "50", "800", "Teal", "600", "100", "800" + ) + screen = MDScreen() + screen.add_widget( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + return screen - class MainApp(MDApp): - def build(self): - self.theme_cls.set_colors( - "Blue", "600", "50", "800", "Teal", "600", "100", "800" - ) - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen + MainApp().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.button import MDRectangleFlatButton + + class MainApp(MDApp): + def build(self): + self.theme_cls.set_colors( + "Blue", "600", "50", "800", "Teal", "600", "100", "800" + ) + return ( + MDScreen( + MDRectangleFlatButton( + text="Hello, World", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) - MainApp().run() - + MainApp().run() """ + self.primary_palette = primary_palette self.primary_hue = primary_hue self.primary_light_hue = primary_light_hue @@ -1116,10 +1551,6 @@ class ThemeManager(EventDispatcher): def __init__(self, **kwargs): super().__init__(**kwargs) - self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas") - self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas") - self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas") - self.round_shadow = Atlas(f"{images_path}round_shadow.atlas") Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style)) self._determine_device_orientation(None, Window.size) Window.bind(size=self._determine_device_orientation) @@ -1167,15 +1598,12 @@ class ThemableBehavior(EventDispatcher): MDSwitch: widget_style: "ios" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch-ios.gif - :align: center - .. code-block:: kv MDSwitch: widget_style: "android" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch-android.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-android-ios.png :align: center :attr:`widget_style` is an :class:`~kivy.properties.OptionProperty` @@ -1208,6 +1636,16 @@ class ThemableBehavior(EventDispatcher): """ def __init__(self, **kwargs): + self.unbind_properties = [ + "theme_style", + "material_style", + "device_orientation", + "primary_color", + "primary_palette", + "accent_palette", + "text_color", + ] + if self.theme_cls is not None: pass else: @@ -1228,3 +1666,24 @@ class ThemableBehavior(EventDispatcher): ) self.theme_cls = App.get_running_app().theme_cls super().__init__(**kwargs) + + # def dec_disabled(self, *args, **kwargs) -> None: + # callabacks = self.theme_cls.get_property_observers("theme_style") + + # for callaback in callabacks: + # try: + # if hasattr(callaback, "proxy") and hasattr( + # callaback.proxy, "theme_cls" + # ): + # for property_name in self.unbind_properties: + # self.theme_cls.unbind( + # **{ + # property_name: getattr( + # callaback.proxy, callaback.method_name + # ) + # } + # ) + # except ReferenceError: + # pass + + # super().dec_disabled(*args, **kwargs) diff --git a/sbapp/kivymd/tools/hotreload/app.py b/sbapp/kivymd/tools/hotreload/app.py index e036875..d3ef77f 100644 --- a/sbapp/kivymd/tools/hotreload/app.py +++ b/sbapp/kivymd/tools/hotreload/app.py @@ -298,8 +298,9 @@ class MDApp(BaseApp): from kivy.core.window import Window from kivy.utils import get_color_from_hex - Window.clearcolor = get_color_from_hex("#e50000") - scroll = Factory.ScrollView(scroll_y=0) + scroll = Factory.MDScrollView( + scroll_y=0, md_bg_color=get_color_from_hex("#e50000") + ) lbl = Factory.Label( text_size=(Window.width - 100, None), size_hint_y=None, diff --git a/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py b/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py index d2cb9a5..040d103 100644 --- a/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py +++ b/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py @@ -13,6 +13,18 @@ from pathlib import Path import kivymd datas = [ + # Add `.frag` files from the `kivymd/data/glsl/elevation` directory. + ( + str(Path(kivymd.glsl_path).joinpath("elevation")) + os.sep, + str( + Path("kivymd").joinpath( + str(Path(kivymd.glsl_path)).split(str(Path("kivymd")) + os.sep)[ + 1 + ] + + f"{os.sep}elevation" + ) + ), + ), # Add `.ttf` files from the `kivymd/fonts` directory. ( kivymd.fonts_path, diff --git a/sbapp/kivymd/tools/patterns/MVC/Controller/__init__.py b/sbapp/kivymd/tools/patterns/MVC/Controller/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sbapp/kivymd/tools/patterns/MVC/Controller/first_screen.py_tmp b/sbapp/kivymd/tools/patterns/MVC/Controller/first_screen.py_tmp deleted file mode 100644 index 1bcf0e8..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/Controller/first_screen.py_tmp +++ /dev/null @@ -1,3 +0,0 @@ -%s - def get_view(self) -> %s: - return self.view diff --git a/sbapp/kivymd/tools/patterns/MVC/Makefile.tmp b/sbapp/kivymd/tools/patterns/MVC/Makefile.tmp deleted file mode 100644 index 4fd0715..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/Makefile.tmp +++ /dev/null @@ -1,26 +0,0 @@ -# FILE TO FIND AND CREATE LOCALIZATION FILES FOR YOUR APPLICATION. \ -\ -In this file, you can specify in which files of your project to search for \ -localization strings. \ -These files should be listed in the below command: \ -\ -\ -xgettext -Lpython --output=messages.pot --from-code=utf-8 \ - path/to/file-1 \ - path/to/file-2 \ - ... - -.PHONY: po mo - -po: - xgettext -Lpython --output=messages.pot --from-code=utf-8 \ - View/%s/%s.kv \ - View/%s/%s.py - msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/en.po messages.pot - msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/ru.po messages.pot - -mo: - mkdir -p data/locales/en/LC_MESSAGES - mkdir -p data/locales/ru/LC_MESSAGES - msgfmt -c -o data/locales/en/LC_MESSAGES/%s.mo data/locales/po/en.po - msgfmt -c -o data/locales/ru/LC_MESSAGES/%s.mo data/locales/po/ru.po \ No newline at end of file diff --git a/sbapp/kivymd/tools/patterns/MVC/Model/base_model.py_tmp b/sbapp/kivymd/tools/patterns/MVC/Model/base_model.py_tmp deleted file mode 100644 index 554a4f4..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/Model/base_model.py_tmp +++ /dev/null @@ -1,33 +0,0 @@ -# The model implements the observer pattern. This means that the class must -# support adding, removing, and alerting observers. In this case, the model is -# completely independent of controllers and views. It is important that all -# registered observers implement a specific method that will be called by the -# model when they are notified (in this case, it is the `model_is_changed` -# method). For this, observers must be descendants of an abstract class, -# inheriting which, the `model_is_changed` method must be overridden. - - -class BaseScreenModel: - """Implements a base class for model modules.""" - - _observers = [] - - def add_observer(self, observer) -> None: - self._observers.append(observer) - - def remove_observer(self, observer) -> None: - self._observers.remove(observer) - - def notify_observers(self, name_screen: str) -> None: - """ - Method that will be called by the observer when the model data changes. - - :param name_screen: - name of the view for which the method should be called - :meth:`model_is_changed`. - """ - - for observer in self._observers: - if observer.name == name_screen: - observer.model_is_changed() - break diff --git a/sbapp/kivymd/tools/patterns/MVC/Model/first_screen.py_tmp b/sbapp/kivymd/tools/patterns/MVC/Model/first_screen.py_tmp deleted file mode 100644 index 3aa8911..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/Model/first_screen.py_tmp +++ /dev/null @@ -1 +0,0 @@ -%s diff --git a/sbapp/kivymd/tools/patterns/MVC/Utility/__init__.py b/sbapp/kivymd/tools/patterns/MVC/Utility/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sbapp/kivymd/tools/patterns/MVC/Utility/observer.py_tmp b/sbapp/kivymd/tools/patterns/MVC/Utility/observer.py_tmp deleted file mode 100644 index bbf1a9f..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/Utility/observer.py_tmp +++ /dev/null @@ -1,16 +0,0 @@ -# Of course, "very flexible Python" allows you to do without an abstract -# superclass at all or use the clever exception `NotImplementedError`. In my -# opinion, this can negatively affect the architecture of the application. -# I would like to point out that using Kivy, one could use the on-signaling -# model. In this case, when the state changes, the model will send a signal -# that can be received by all attached observers. This approach seems less -# universal - you may want to use a different library in the future. - - -class Observer: - """Abstract superclass for all observers.""" - - def model_is_changed(self): - """ - The method that will be called on the observer when the model changes. - """ diff --git a/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/__init__.py b/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.kv b/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.kv deleted file mode 100644 index 9d8c3b0..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.kv +++ /dev/null @@ -1,72 +0,0 @@ -#:import images_path kivymd.images_path -#:import colors kivymd.color_definitions.colors -#:import get_color_from_hex kivy.utils.get_color_from_hex - - -<%s> - - FitImage: - source: - ( \ - f"{images_path}restdb-logo.png" \ - if root.model.database.name == "RestDB" else \ - f"{images_path}firebase-logo.png" \ - ) \ - if hasattr(root.model, "database") else \ - f"{images_path}transparent.png" - - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - id: toolbar - title: "%s" - right_action_items: [["web", lambda x: %s]] - md_bg_color: - ( \ - get_color_from_hex(colors["Yellow"]["700"]) \ - if root.model.database.name == "Firebase" else \ - get_color_from_hex(colors["Blue"]["300"]) \ - ) \ - if hasattr(root.model, "database") else \ - app.theme_cls.primary_color - - MDFloatLayout: - - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - size_hint_x: None - width: root.width - dp(72) - radius: 12 - padding: "12dp" - md_bg_color: 1, 1, 1, .5 - pos_hint: {"center_x": .5, "center_y": .5} - - MDLabel: - id: prev_label - text: %s - font_style: "H6" - adaptive_height: True - halign: "center" - color: 1, 1, 1, 1 - - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - padding: "50dp" - spacing: "20dp" - - MDTextField: - hint_text: %s - on_text: root.controller.set_user_data("login", self.text) - - MDTextField: - hint_text: %s - on_text: root.controller.set_user_data("password", self.text) - - MDFillRoundFlatButton: - text: %s - on_release: root.controller.on_tap_button_login() - pos_hint: {"center_x": .5, "center_y": .1} - md_bg_color: toolbar.md_bg_color diff --git a/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.py_tmp b/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.py_tmp deleted file mode 100644 index f063c31..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/View/FirstScreen/first_screen.py_tmp +++ /dev/null @@ -1,15 +0,0 @@ -%s -from View.base_screen import BaseScreenView - - -class %s(BaseScreenView): - """Implements the login start screen in the user application.""" -%s - def model_is_changed(self) -> None: - """ - Called whenever any change has occurred in the data model. - The view in this method tracks these changes and updates the UI - according to these changes. - """ - - %s \ No newline at end of file diff --git a/sbapp/kivymd/tools/patterns/MVC/View/base_screen.py_tmp b/sbapp/kivymd/tools/patterns/MVC/View/base_screen.py_tmp deleted file mode 100644 index 62a4242..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/View/base_screen.py_tmp +++ /dev/null @@ -1,47 +0,0 @@ -from kivy.properties import ObjectProperty - -from kivymd.app import MDApp -from kivymd.theming import ThemableBehavior -from kivymd.uix.screen import MDScreen - -from Utility.observer import Observer - - -class BaseScreenView(ThemableBehavior, MDScreen, Observer): - """ - A base class that implements a visual representation of the model data - :class:`~Model.%s.%s`. - The view class must be inherited from this class. - """ - - controller = ObjectProperty() - """ - Controller object - :class:`~Controller.%s.%s`. - - :attr:`controller` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - model = ObjectProperty() - """ - Model object - :class:`~Model.%s.%s`. - - :attr:`model` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - manager_screens = ObjectProperty() - """ - Screen manager object - :class:`~kivy.uix.screenmanager.ScreenManager`. - - :attr:`manager_screens` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - def __init__(self, **kw): - super().__init__(**kw) - # Often you need to get access to the application object from the view - # class. You can do this using this attribute. - self.app = MDApp.get_running_app() - # Adding a view class as observer. - self.model.add_observer(self) diff --git a/sbapp/kivymd/tools/patterns/MVC/View/screens.py_tmp b/sbapp/kivymd/tools/patterns/MVC/View/screens.py_tmp deleted file mode 100644 index f1ab8c3..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/View/screens.py_tmp +++ /dev/null @@ -1,13 +0,0 @@ -# The screens dictionary contains the objects of the models and controllers -# of the screens of the application. - -from Model.%s import %s - -from Controller.%s import %s - -screens = { - %s: { - "model": %s, - "controller": %s, - }, -} diff --git a/sbapp/kivymd/tools/patterns/MVC/main.py_tmp b/sbapp/kivymd/tools/patterns/MVC/main.py_tmp deleted file mode 100644 index d697286..0000000 --- a/sbapp/kivymd/tools/patterns/MVC/main.py_tmp +++ /dev/null @@ -1,54 +0,0 @@ -""" -The entry point to the application. - -The application uses the MVC template. Adhering to the principles of clean -architecture means ensuring that your application is easy to test, maintain, -and modernize. - -You can read more about this template at the links below: - -https://github.com/HeaTTheatR/LoginAppMVC -https://en.wikipedia.org/wiki/Model–view–controller -""" - -from typing import NoReturn - -from kivy.uix.screenmanager import ScreenManager%s - -from kivymd.app import MDApp - -from View.screens import screens%s -%s - -class %s(MDApp):%s - def __init__(self, **kwargs): - super().__init__(**kwargs)%s - self.load_all_kv_files(self.directory) - # This is the screen manager that will contain all the screens of your - # application. - self.manager_screens = ScreenManager() - %s - def build(self) -> ScreenManager: - self.generate_application_screens() - return self.manager_screens - - def generate_application_screens(self) -> NoReturn: - """ - Creating and adding screens to the screen manager. - You should not change this cycle unnecessarily. He is self-sufficient. - - If you need to add any screen, open the `View.screens.py` module and - see how new screens are added according to the given application - architecture. - """ - - for i, name_screen in enumerate(screens.keys()): - model = screens[name_screen]["model"](%s) - controller = screens[name_screen]["controller"](model) - view = controller.get_view() - view.manager_screens = self.manager_screens - view.name = name_screen - self.manager_screens.add_widget(view) -%s%s - -%s().run() diff --git a/sbapp/kivymd/tools/patterns/add_view.py b/sbapp/kivymd/tools/patterns/add_view.py new file mode 100644 index 0000000..0187f01 --- /dev/null +++ b/sbapp/kivymd/tools/patterns/add_view.py @@ -0,0 +1,210 @@ +""" +The script creates a new View package +===================================== + +The script creates a new View package in an existing project with an MVC +template created using the create_project utility. + +.. versionadded:: 1.0.0 + +.. seealso:: + + `Utility create_project `_ + +.. rubric:: Use a clean architecture for your applications. + +To add a new view to an existing project that was created using the +`create_project` utility, use the following command:: + + kivymd.add_view \\ + name_pattern \\ + path_to_project \\ + name_view + +Example command:: + + kivymd.add_view \\ + MVC \\ + /Users/macbookair/Projects \\ + NewScreen + +You can also add new views with responsive behavior to an existing project:: + + kivymd.add_view \\ + MVC \\ + /Users/macbookair/Projects \\ + NewScreen \\ + --use_responsive yes + +For more information about adaptive design, +`see here `_. +""" + +__all__ = [ + "main", +] + +import os +import re + +from kivy import Logger + +from kivymd.tools.argument_parser import ArgumentParserWithHelp +from kivymd.tools.patterns.create_project import ( + chek_camel_case_name_project, + create_common_responsive_module, + create_controller, + create_model, + create_view, +) + +screens_data = """%s + +screens = {%s +}""" + +screns_comment = """# The screen's dictionary contains the objects of the models and controllers +# of the screens of the application. +""" + + +def main(): + """The function of adding a new view to the project.""" + + global screens_data + + parser = create_argument_parser() + args = parser.parse_args() + + # pattern_name isn't used currently, will be used if new patterns is added in future + pattern_name = args.pattern # noqa F841 + path_to_project = args.directory + name_view = args.name + use_responsive = args.use_responsive + + if not os.path.exists(path_to_project): + parser.error(f"Project <{path_to_project}> does not exist...") + + if name_view[-6:] != "Screen": + parser.error( + f"The name of the <{name_view}> screen should contain the word " + f"'Screen' at the end.\n" + "For example - '--name_screen MyFirstScreen ...'" + ) + + if name_view in os.listdir(os.path.join(path_to_project, "View")): + parser.error( + f"The <{name_view}> view also exists in the <{path_to_project}> project..." + ) + + # Create model. + name_database = ( + "yes" + if "database.py" in os.listdir(os.path.join(path_to_project, "Model")) + else "no" + ) + module_name = chek_camel_case_name_project(name_view) + if not module_name: + parser.error( + "The name of the screen should be written in camel case style. " + "\nFor example - 'MyFirstScreen'" + ) + module_name = "_".join([name.lower() for name in module_name]) + path_to_project = path_to_project + create_model(name_view, module_name, name_database, path_to_project) + + # Create controller. + # FIXME: This is not a very good solution in order to understand whether + # a project uses a hot reload or not. Because the string + # 'from kivymd.tools.hotreload.app import MDApp' in the project can just + # be commented out and the project does not actually use hot reload. + with open(os.path.join(path_to_project, "main.py")) as main_module: + if "from kivymd.tools.hotreload.app import MDApp" in main_module.read(): + use_hotreload = "yes" + else: + use_hotreload = "no" + create_controller( + name_view, module_name, use_hotreload, path_to_project + ) + # Create View. + if use_responsive == "no": + create_view(name_view, module_name, [], path_to_project) + else: + create_view(name_view, module_name, [name_view], path_to_project) + create_common_responsive_module([name_view], path_to_project) + # Create 'View.screens.py module'. + create_screens_data(name_view, module_name, path_to_project) + Logger.info( + f"KivyMD: The {name_view} view has been added to the project..." + ) + + +def create_screens_data( + name_view: str, module_name: str, path_to_project: str +) -> None: + with open( + os.path.join(path_to_project, "View", "screens.py") + ) as screen_module: + screen_module = screen_module.read() + imports = re.findall( + "from Model.*Model|from Controller.*Controller", screen_module + ) + screens = "" + path_to_view = os.path.join(path_to_project, "View") + + for name in os.listdir(path_to_view): + if os.path.isdir(os.path.join(path_to_view, name)): + res = re.findall("[A-Z][a-z]*", name) + if res and len(res) == 2 and res[-1] == "Screen": + screens += ( + "\n '%s': {" + "\n 'model': %s," + "\n 'controller': %s," + "\n }," + % ( + f"{res[0].lower()} {res[1].lower()}", + f'{"".join(res)}Model', + f'{"".join(res)}Controller', + ) + ) + + imports.append(f"from Model.{module_name} import {name_view}Model") + imports.append( + f"from Controller.{module_name} import {name_view}Controller" + ) + imports.insert(0, screns_comment) + screens = screens_data % ("\n".join(imports), screens) + + with open( + os.path.join(path_to_project, "View", "screens.py"), "w" + ) as screen_module: + screen_module.write(screens) + + +def create_argument_parser() -> ArgumentParserWithHelp: + parser = ArgumentParserWithHelp( + prog="create_project.py", + allow_abbrev=False, + ) + parser.add_argument( + "pattern", + help="the name of the pattern with which the project will be created.", + ) + parser.add_argument( + "directory", + help="the directory of the project to which you want to add a new view.", + ) + parser.add_argument( + "name", + help="the name of the view to add to an existing project.", + ) + parser.add_argument( + "--use_responsive", + default="no", + help="whether to create a view with responsive behavior.", + ) + return parser + + +if __name__ == "__main__": + main() diff --git a/sbapp/kivymd/tools/patterns/create_project.py b/sbapp/kivymd/tools/patterns/create_project.py index de59c1a..64487b0 100644 --- a/sbapp/kivymd/tools/patterns/create_project.py +++ b/sbapp/kivymd/tools/patterns/create_project.py @@ -25,7 +25,7 @@ Project creation Template command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ name_pattern \\ path_to_project \\ name_project \\ @@ -34,7 +34,7 @@ Template command:: Example command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ MVC \\ /Users/macbookair/Projects \\ MyMVCProject \\ @@ -61,7 +61,7 @@ Creating a project using a database Template command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ name_pattern \\ path_to_project \\ name_project \\ @@ -71,7 +71,7 @@ Template command:: Example command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ MVC \\ /Users/macbookair/Projects \\ MyMVCProject \\ @@ -99,18 +99,12 @@ the database restdb.io. `database_url` and `api_key` parameters on the test database (works only in read mode), so you should use your data for the database. -Preview of the basic MVC template using the RestDB database ------------------------------------------------------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-restbd-preview.png - :align: center - Create project with hot reload ------------------------------ Template command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ name_pattern \\ path_to_project \\ name_project \\ @@ -120,7 +114,7 @@ Template command:: Example command:: - python -m kivymd.tools.patterns.create_project \\ + kivymd.create_project \\ MVC \\ /Users/macbookair/Projects \\ MyMVCProject \\ @@ -133,8 +127,31 @@ information. Also, the necessary information is in other modules of the project in the form of comments. So do not forget to look at the source files of the created project. +Create project with responsive view +----------------------------------- + +When creating a project, you can specify which views should use responsive +behavior. To do this, specify the name of the view/views in the +`--use_responsive` argument: + +Template command:: + + kivymd.create_project \\ + name_pattern \\ + path_to_project \\ + name_project \\ + python_version \\ + kivy_version \\ + --name_screen FirstScreen SecondScreen ThirdScreen \\ + --use_responsive FirstScreen SecondScreen + +The `FirstScreen` and `SecondScreen` views will be created with an responsive +architecture. For more detailed information about using the adaptive view, see +the `MDResponsiveLayout `_ +widget. + Others command line arguments -====================== +============================= Required Arguments ------------------ @@ -161,6 +178,20 @@ Optional arguments - name_screen - the name of the class which be used when creating the project pattern +When you need to create an application template with multiple screens, +use multiple values separated by a space for the `name_screen` parameter, +for example, as shown below: + +Template command:: + + kivymd.create_project \\ + name_pattern \\ + path_to_project \\ + name_project \\ + python_version \\ + kivy_version \\ + --name_screen FirstScreen SecondScreen ThirdScreen + - name_database - provides a basic template for working with the 'firebase' library - or a complete implementation for working with a database 'restdb.io' @@ -168,14 +199,21 @@ Optional arguments - use_hotreload - creates a hot reload entry point to the application --use_localization +- use_localization - creates application localization files +- use_responsive + - the name/names of the views to be used by the responsive UI + .. warning:: On Windows, hot reloading of Python files may not work. But, for example, there is no such problem in macOS. If you fix this, please report it to the KivyMD community. """ +__all__ = [ + "main", +] + import os import re import shutil @@ -186,7 +224,42 @@ from kivy import Logger, platform from kivymd import path as kivymd_path from kivymd.tools.argument_parser import ArgumentParserWithHelp -_database_model = '''import multitasking +temp_basemodel = '''# The model implements the observer pattern. This means that the class must +# support adding, removing, and alerting observers. In this case, the model is +# completely independent of controllers and views. It is important that all +# registered observers implement a specific method that will be called by the +# model when they are notified (in this case, it is the `model_is_changed` +# method). For this, observers must be descendants of an abstract class, +# inheriting which, the `model_is_changed` method must be overridden. + + +class BaseScreenModel: + """Implements a base class for model modules.""" + + _observers = [] + + def add_observer(self, observer) -> None: + self._observers.append(observer) + + def remove_observer(self, observer) -> None: + self._observers.remove(observer) + + def notify_observers(self, name_screen: str) -> None: + """ + Method that will be called by the observer when the model data changes. + + :param name_screen: + name of the view for which the method should be called + :meth:`model_is_changed`. + """ + + for observer in self._observers: + if observer.name == name_screen: + observer.model_is_changed() + break +''' + +temp_database_model = '''import multitasking from Model.base_model import BaseScreenModel @@ -200,20 +273,16 @@ class {name_screen}Model(BaseScreenModel): """ def __init__(self, database): - self.database = database - # Dict: - # 'login': 'KivyMD' - # 'password': '1111' - self.user_data = dict() - self._data_validation_status = None + # Just an example of the data. Use your own values. + self._data = None @property - def data_validation_status(self): - return self._data_validation_status + def data(self): + return self._data - @data_validation_status.setter - def data_validation_status(self, value): - self._data_validation_status = value + @data.setter + def data(self, value): + self._data = value # We notify the View - # :class:`~View.{name_screen}.{module_name}.{name_screen}View` about the # changes that have occurred in the data model. @@ -221,37 +290,12 @@ class {name_screen}Model(BaseScreenModel): @multitasking.task def check_data(self): - """ - Get data from the database and compares this data with the data entered - by the user. This method is completely asynchronous. - It does not return any value. - """ + """Just an example of the method. Use your own code.""" - data = self.database.get_data_from_collection(self.database.USER_DATA) - data_validation_status = False - - if data: - if self.database.name == "RestDB": - data = data[0] - else: - data = list(data.values())[0] - if ( - data["login"] == self.user_data["login"] - and data["password"] == self.user_data["password"] - ): - data_validation_status = True - self.data_validation_status = data_validation_status - - def set_user_data(self, key: str, value: str) -> None: - """Sets a dictionary of data that the user enters.""" - - self.user_data[key] = value - - def reset_data_validation_status(self) -> None: - self.data_validation_status = None + self.data = ["example item"] ''' -_without_database_model = '''from Model.base_model import BaseScreenModel +temp_without_database_model = '''from Model.base_model import BaseScreenModel class {name_screen}Model(BaseScreenModel): @@ -260,41 +304,62 @@ class {name_screen}Model(BaseScreenModel): :class:`~View.{module_name}.{name_screen}.{name_screen}View` class. """''' -_database_controller = ''' -{import_module} +temp_screens_imports = """# The screens dictionary contains the objects of the models and controllers +# of the screens of the application. -class {name_screen}Controller: - """ - The `{name_screen}Controller` class represents a controller implementation. - Coordinates work of the view with the model. - The controller implements the strategy pattern. The controller connects to - the view to control its actions. - """ +""" - def __init__(self, model): - self.model = model # Model.{module_name}.{name_screen}Model object - self.view = {name_view}( - controller=self, model=self.model - ) +temp_code_responsive_view = '''from kivymd.uix.responsivelayout import MDResponsiveLayout - def set_user_data(self, key: str, value: str) -> None: - """Called every time the user enters text into the text fields.""" +from View.{name_screen}.components import ( + MobileScreenView, + TabletScreenView, + DesktopScreenView, +) +from View.base_screen import BaseScreenView - self.model.set_user_data(key, value) - def on_tap_button_login(self) -> None: - """Called when the `LOGIN` button is pressed.""" +class {name_screen}View(MDResponsiveLayout, BaseScreenView): + def __init__(self, **kw): + super().__init__(**kw) + self.mobile_view = MobileScreenView() + self.tablet_view = TabletScreenView() + self.desktop_view = DesktopScreenView() - self.view.show_dialog_wait() - self.model.check_data() - - def reset_data_validation_status(self, *args) -> None: - self.model.reset_data_validation_status() + def model_is_changed(self) -> None: + """ + Called whenever any change has occurred in the data model. + The view in this method tracks these changes and updates the UI + according to these changes. + """ ''' -_without_database_controller = ''' -{import_module} +temp_responsive_component_imports = """from .platforms.MobileScreen.mobile_screen import MobileScreenView +from .platforms.TabletScreen.tablet_screen import TabletScreenView +from .platforms.DesktopScreen.desktop_screen import DesktopScreenView +""" + +temp_responsive_platform_baseclass = """from kivymd.uix.screen import MDScreen + + +class {}View(MDScreen): + pass +""" + +temp_code_view = '''from View.base_screen import BaseScreenView + + +class {name_screen}View(BaseScreenView): + def model_is_changed(self) -> None: + """ + Called whenever any change has occurred in the data model. + The view in this method tracks these changes and updates the UI + according to these changes. + """ +''' + +temp_code_controller = '''{import_module} class {name_screen}Controller: @@ -309,71 +374,78 @@ class {name_screen}Controller: self.model = model # Model.{module_name}.{name_screen}Model self.view = {name_view}(controller=self, model=self.model) - def on_tap_button_login(self) -> None: - """Called when the `LOGIN` button is pressed.""" - - def set_user_data(self, key, value) -> None: - """Called every time the user enters text into the text fields.""" + def get_view(self) -> {get_view}: + return self.view ''' -_database_view_import = """from typing import Union +temp_base_screen = '''from kivy.properties import ObjectProperty -from kivy.clock import Clock +from kivymd.app import MDApp +from kivymd.theming import ThemableBehavior +from kivymd.uix.screen import MDScreen -from kivymd.uix.dialog import MDDialog -from kivymd.uix.snackbar import Snackbar -""" +from Utility.observer import Observer -_without_database_view_import = """ -""" -_database_view_methods = ''' def __init__(self, **kwargs): - super().__init__(**kwargs) - self.dialog = MDDialog() - self.dialog.bind(on_dismiss=self.controller.reset_data_validation_status) +class BaseScreenView(ThemableBehavior, MDScreen, Observer): + """ + A base class that implements a visual representation of the model data. + The view class must be inherited from this class. + """ - def show_dialog_wait(self) -> None: - """Displays a wait dialog while the model is processing data.""" + controller = ObjectProperty() + """ + Controller object - :class:`~Controller.controller_screen.ClassScreenControler`. - self.dialog.auto_dismiss = False - self.dialog.text = "Data validation..." - self.dialog.open() + :attr:`controller` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ - def show_toast(self, interval: Union[int, float]) -> None: - Snackbar( - text="You have passed the verification successfully!", - snackbar_x="10dp", - snackbar_y="10dp", - size_hint_x=.8, - bg_color=self.theme_cls.primary_color, - ).open() + model = ObjectProperty() + """ + Model object - :class:`~Model.model_screen.ClassScreenModel`. + + :attr:`model` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + manager_screens = ObjectProperty() + """ + Screen manager object - :class:`~kivymd.uix.screenmanager.MDScreenManager`. + + :attr:`manager_screens` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def __init__(self, **kw): + super().__init__(**kw) + # Often you need to get access to the application object from the view + # class. You can do this using this attribute. + self.app = MDApp.get_running_app() + # Adding a view class as observer. + self.model.add_observer(self) ''' -_database_view_model_is_changed_method = """if self.model.data_validation_status: - self.dialog.dismiss() - Clock.schedule_once(self.show_toast, 1) - if self.model.data_validation_status is False: - self.dialog.text = "Wrong data!" - self.dialog.auto_dismiss = True -""" +temp_utility = ''' +# Of course, "very flexible Python" allows you to do without an abstract +# superclass at all or use the clever exception `NotImplementedError`. In my +# opinion, this can negatively affect the architecture of the application. +# I would like to point out that using Kivy, one could use the on-signaling +# model. In this case, when the state changes, the model will send a signal +# that can be received by all attached observers. This approach seems less +# universal - you may want to use a different library in the future. -_firebase_requirements = """kivy==2.1.0 -kivymd==1.0.0 -multitasking -firebase -firebase-admin -python_jwt -gcloud -sseclient -pycryptodome==3.4.3 -requests_toolbelt -""" -_without_firebase_requirements = """kivy==2.1.0 -kivymd==1.0.0 -""" +class Observer: + """Abstract superclass for all observers.""" -_hot_reload_main = ''' + def model_is_changed(self): + """ + The method that will be called on the observer when the model changes. + """ +''' + +temp_hot_reload_main = ''' """ Script for managing hot reloading of the project. For more details see the documentation page - @@ -388,7 +460,6 @@ import importlib import os from kivy import Config -from kivy.uix.screenmanager import ScreenManager from PIL import ImageGrab @@ -399,26 +470,20 @@ resolution = ImageGrab.grab().size Config.set("graphics", "height", resolution[1]) Config.set("graphics", "width", "400") -from kivy.core.window import Window%s +from kivy.core.window import Window{} # Place the application window on the right side of the computer screen. Window.top = 0 Window.left = resolution[0] - Window.width from kivymd.tools.hotreload.app import MDApp -%s%s +from kivymd.uix.screenmanager import MDScreenManager +{}{} -class %s(MDApp): - KV_FILES = { - os.path.join( - os.getcwd(), - "View", - "%s", - "%s.kv", - ), - }%s +class {}(MDApp): + KV_DIRS = [os.path.join(os.getcwd(), "View")]{} - def build_app(self) -> ScreenManager: + def build_app(self) -> MDScreenManager: """ In this method, you don't need to change anything other than the application theme. @@ -426,14 +491,13 @@ class %s(MDApp): import View.screens - self.theme_cls.primary_palette = "Orange" - self.manager_screens = ScreenManager()%s%s + self.manager_screens = MDScreenManager(){}{} Window.bind(on_key_down=self.on_keyboard_down) importlib.reload(View.screens) screens = View.screens.screens for i, name_screen in enumerate(screens.keys()): - model = screens[name_screen]["model"](%s) + model = screens[name_screen]["model"]({}) controller = screens[name_screen]["controller"](model) view = controller.get_view() view.manager_screens = self.manager_screens @@ -451,20 +515,138 @@ class %s(MDApp): """ if "meta" in modifiers or "ctrl" in modifiers and text == "r": - self.rebuild()%s%s + self.rebuild(){}{} -%s().run() +{}().run() # After you finish the project, remove the above code and uncomment the below # code to test the application normally without hot reloading. ''' +temp_main = '''""" +The entry point to the application. + +The application uses the MVC template. Adhering to the principles of clean +architecture means ensuring that your application is easy to test, maintain, +and modernize. + +You can read more about this template at the links below: + +https://github.com/HeaTTheatR/LoginAppMVC +https://en.wikipedia.org/wiki/Model–view–controller +""" +{} +from kivymd.app import MDApp +from kivymd.uix.screenmanager import MDScreenManager + +from View.screens import screens{} +{} + +class {}(MDApp):{} + def __init__(self, **kwargs): + super().__init__(**kwargs){} + self.load_all_kv_files(self.directory) + # This is the screen manager that will contain all the screens of your + # application. + self.manager_screens = MDScreenManager() + {} + def build(self) -> MDScreenManager: + self.generate_application_screens() + return self.manager_screens + + def generate_application_screens(self) -> None: + """ + Creating and adding screens to the screen manager. + You should not change this cycle unnecessarily. He is self-sufficient. + + If you need to add any screen, open the `View.screens.py` module and + see how new screens are added according to the given application + architecture. + """ + + for i, name_screen in enumerate(screens.keys()): + model = screens[name_screen]["model"]({}) + controller = screens[name_screen]["controller"](model) + view = controller.get_view() + view.manager_screens = self.manager_screens + view.name = name_screen + self.manager_screens.add_widget(view) +{}{} + +{}().run() +''' + +temp_makefile = """# FILE TO FIND AND CREATE LOCALIZATION FILES FOR YOUR APPLICATION. \\ +\\ +In this file, you can specify in which files of your project to search for \\ +localization strings. \\ +These files should be listed in the below command: \\ +\\ +\\ +xgettext -Lpython --output=messages.pot --from-code=utf-8 \\ + path/to/file-1 \\ + path/to/file-2 \\ + ... + +.PHONY: po mo + +po: + xgettext -Lpython --output=messages.pot --from-code=utf-8 \\ +{} + msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/en.po messages.pot + msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/ru.po messages.pot + +mo: + mkdir -p data/locales/en/LC_MESSAGES + mkdir -p data/locales/ru/LC_MESSAGES + msgfmt -c -o data/locales/en/LC_MESSAGES/%s.mo data/locales/po/en.po + msgfmt -c -o data/locales/ru/LC_MESSAGES/%s.mo data/locales/po/ru.po +""" + +firebase_requirements = """kivy==2.1.0 +kivymd==1.0.0 +multitasking +firebase +firebase-admin +python_jwt +gcloud +sseclient +pycryptodome==3.4.3 +requests_toolbelt +""" + +without_firebase_requirements = """kivy==2.1.0 +kivymd==1.0.0 +""" + available_patterns = ["MVC"] available_databases = ["firebase", "restdb"] +path_to_project = "" +project_name = "" +use_localization = "" +name_database = "" +use_hotreload = "" +temp_makefile_files = "" +temp_screens_data = "" +kivy_version = "" +python_version = "" + def main(): + """Project creation function.""" + + global project_name + global use_localization + global name_database + global use_hotreload + global temp_makefile_files + global temp_screens_data + global path_to_project + global kivy_version + global python_version + parser = create_argument_parser() args = parser.parse_args() @@ -475,7 +657,7 @@ def main(): python_version = args.python_version if "3" not in python_version: parser.error("Python must be at least version 3") - name_screen = "".join(args.name_screen.split(" ")) + name_screen = args.name_screen path_to_project = os.path.join(project_directory, project_name) name_database = args.name_database if name_database != "no" and name_database not in available_databases: @@ -484,20 +666,16 @@ def main(): ) use_hotreload = args.use_hotreload use_localization = args.use_localization + use_responsive = args.use_responsive # Check arguments. - if name_screen[-6:] != "Screen": - parser.error( - "Name of the screen must contain the word 'Screen' at the end. " - "\nFor example - '... --name_screen MyFirstScreen'" - ) - module_name = chek_camel_case_name_project(name_screen) - if not module_name: - parser.error( - "The name of the screen should be written in camel case style. " - "\nFor example - 'MyFirstScreen'" - ) - module_name = "_".join([name.lower() for name in module_name]) + for name in name_screen: + if name[-6:] != "Screen": + parser.error( + f"The name of the {name} screen should contain the word " + f"'Screen' at the end.\n" + "For example - '--name_screen MyFirstScreen ...'" + ) if not os.path.exists( os.path.join(kivymd_path, "tools", "patterns", pattern_name) ): @@ -512,42 +690,53 @@ def main(): os.path.join(kivymd_path, "tools", "patterns", pattern_name), path_to_project, ) - create_main( - name_database, use_localization, path_to_project, project_name - ) - create_model(name_database, name_screen, module_name, path_to_project) - create_controller( - name_database, - use_hotreload, - name_screen, - module_name, - path_to_project, - ) - create_view( - name_database, - use_localization, - name_screen, - module_name, - path_to_project, - ) - create_requirements(name_database, path_to_project) + create_main() + + for name in name_screen: + module_name = chek_camel_case_name_project(name) + if not module_name: + parser.error( + "The name of the screen should be written in camel case style. " + "\nFor example - 'MyFirstScreen'" + ) + module_name = "_".join([name.lower() for name in module_name]) + + # Create models module. + create_model(name, module_name, name_database, path_to_project) + # Create controllers module. + create_controller(name, module_name, use_hotreload, path_to_project) + # Create screens data. + create_screens_data(name, module_name) + if use_localization == "yes": + # Create makefile data. + create_makefile_data(name, module_name) + # Create views. + create_view(name, module_name, use_responsive, path_to_project) + + # Create module `NameProject/View/NameScreen/components/common/__init__.py`. + create_common_responsive_module(use_responsive, path_to_project) + # Create module `NameProject/View/screens.py`. + create_module_screens() + # Create module `NameProject/Model/base_model.py`. + create_basemodel() + # Create module `NameProject/View/base_screen.py`. + create_module_basescreen() + # Create package `NameProject/Utility`. + create_package_utility() + # Create file `NameProject/Makefile`. + if use_localization == "yes": + # Create makefile data. + create_makefile() + + create_requirements() os.makedirs(os.path.join(path_to_project, "assets", "images")) os.mkdir(os.path.join(path_to_project, "assets", "fonts")) - rename_ext_py_tmp_to_py(path_to_project) - move_init(path_to_project, name_screen) if name_database != "no": - check_databases(name_database, path_to_project) + check_databases() if use_hotreload == "yes": - create_main_with_hotreload( - path_to_project, - project_name, - name_screen, - module_name, - name_database, - use_localization, - ) + create_main_with_hotreload() with open( os.path.join(path_to_project, "requirements.txt"), "a", @@ -557,36 +746,36 @@ def main(): if use_localization == "yes": Logger.info("KivyMD: Create localization files...") - create_makefile( - path_to_project, project_name, module_name, name_screen - ) - localization_po_file(path_to_project) - create_mofile(path_to_project) + os.chdir(path_to_project) + os.system("make po") + os.system("make mo") else: os.remove(os.path.join(path_to_project, "messages.pot")) os.remove(os.path.join(path_to_project, "libs", "translation.py")) shutil.rmtree(os.path.join(path_to_project, "data")) + Logger.info(f"KivyMD: Project '{path_to_project}' created") Logger.info( f"KivyMD: Create a virtual environment for '{path_to_project}' project..." ) - create_virtual_environment(python_version, path_to_project) + create_virtual_environment() Logger.info( f"KivyMD: Install requirements for '{path_to_project}' project..." ) - install_requirements(path_to_project, kivy_version, name_database) + install_requirements() + os.remove(os.path.join(path_to_project, "__init__.py")) + if name_database == "no": + os.remove( + os.path.join(path_to_project, "Model", "database_firebase.py") + ) + os.remove( + os.path.join(path_to_project, "Model", "database_restdb.py") + ) else: parser.error(f"The {path_to_project} project already exists") -def create_main_with_hotreload( - path_to_project: str, - project_name: str, - name_screen: str, - module_name: str, - name_database: str, - use_localization: str, -) -> None: +def create_main_with_hotreload() -> None: with open( os.path.join(path_to_project, "main.py"), encoding="utf-8" ) as main_file: @@ -596,12 +785,14 @@ def create_main_with_hotreload( with open( os.path.join(path_to_project, "main.py"), "w", encoding="utf-8" ) as main_file: - main_file.write(f"{_hot_reload_main}\n{main_code}") + main_file.write(f"{temp_hot_reload_main}\n{main_code}") - replace_in_file( - os.path.join(path_to_project, "main.py"), - ( - "\nfrom kivy.properties import StringProperty" + with open( + os.path.join(path_to_project, "main.py"), encoding="utf-8" + ) as main_file: + main_code = main_file.read() + main_code = main_code.format( + "\nfrom kivy.properties import StringProperty\n" if use_localization == "yes" else "", "\nfrom Model.database import DataBase" @@ -611,8 +802,6 @@ def create_main_with_hotreload( if use_localization == "yes" else "", project_name, - name_screen, - module_name, '\n lang = StringProperty("en")\n' if use_localization == "yes" else "", @@ -620,7 +809,7 @@ def create_main_with_hotreload( if name_database != "no" else "", "\n self.translation = Translation(\n" - ' self.lang, "%s", f"{self.directory}/data/locales"' + ' self.lang, "%s", os.path.join(self.directory, "data", "locales")' "\n )" % project_name if use_localization == "yes" else "", @@ -635,101 +824,98 @@ def create_main_with_hotreload( if use_localization == "yes" else "", project_name, - ), - ) + ) + with open( + os.path.join(path_to_project, "main.py"), "w", encoding="utf-8" + ) as main_module: + main_module.write(main_code) -def create_main( - name_database: str, - use_localization: str, - path_to_project: str, - project_name: str, -) -> None: - replace_in_file( - os.path.join(path_to_project, "main.py_tmp"), - ( - "\nfrom kivy.properties import StringProperty" - if use_localization == "yes" - else "", - "\nfrom libs.translation import Translation" - if use_localization == "yes" - else "", - "from Model.database import DataBase\n" - if name_database != "no" - else "", - project_name, - '\n lang = StringProperty("en")\n' - if use_localization == "yes" - else "", - "\n self.translation = Translation(\n" - ' self.lang, "%s", f"{self.directory}/data/locales"' - "\n )" % project_name - if use_localization == "yes" - else "", - "self.database = DataBase()\n" if name_database != "no" else "", - "self.database" if name_database != "no" else "", - "\n def on_lang(self, instance_app, lang_value: str) -> None:\n" - " self.translation.switch_lang(lang_value)\n" - if use_localization == "yes" - else "", - "\n def switch_lang(self) -> None:\n" - ' """Switch lang."""\n\n' - ' self.lang = "ru" if self.lang == "en" else "en"\n' - if use_localization == "yes" - else "", - project_name, - ), +def create_main() -> None: + main_code = temp_main.format( + "\nfrom kivy.properties import StringProperty\n" + if use_localization == "yes" + else "", + "\nfrom libs.translation import Translation" + if use_localization == "yes" + else "", + "from Model.database import DataBase\n" + if name_database != "no" + else "", + project_name, + '\n lang = StringProperty("en")\n' + if use_localization == "yes" + else "", + "\n self.translation = Translation(\n" + ' self.lang, "%s", os.path.join(self.directory, "data", "locales")' + "\n )" % project_name + if use_localization == "yes" + else "", + "self.database = DataBase()\n" if name_database != "no" else "", + "self.database" if name_database != "no" else "", + "\n def on_lang(self, instance_app, lang_value: str) -> None:\n" + " self.translation.switch_lang(lang_value)\n" + if use_localization == "yes" + else "", + "\n def switch_lang(self) -> None:\n" + ' """Switch lang."""\n\n' + ' self.lang = "ru" if self.lang == "en" else "en"\n' + if use_localization == "yes" + else "", + project_name, ) + with open( + os.path.join(path_to_project, "main.py"), "w", encoding="utf-8" + ) as main_module: + main_module.write(main_code) def create_model( - name_database: str, name_screen: str, module_name: str, path_to_project: str + name_screen: str, module_name: str, name_database: str, path_to_project: str ) -> None: if name_database != "no": - database_model = _database_model.format( + code_model = temp_database_model.format( name_screen=name_screen, module_name=module_name, notify_name_screen=f'"{" ".join(module_name.split("_"))}"', ) - replace_in_file( - os.path.join(path_to_project, "Model", "first_screen.py_tmp"), - (database_model), - ) else: - without_database_model = _without_database_model.format( + code_model = temp_without_database_model.format( module_name=module_name, name_screen=name_screen ) - replace_in_file( - os.path.join(path_to_project, "Model", "first_screen.py_tmp"), - (without_database_model), - ) - os.remove( - os.path.join(path_to_project, "Model", "database_firebase.py") - ) - os.remove(os.path.join(path_to_project, "Model", "database_restdb.py")) - os.rename( - os.path.join(path_to_project, "Model", "first_screen.py_tmp"), - os.path.join(path_to_project, "Model", f"{module_name}.py_tmp"), - ) + + model_module = os.path.join(path_to_project, "Model", module_name) + with open(f"{model_module}.py", "w", encoding="utf-8") as module: + module.write(code_model) + + +def create_basemodel() -> None: + with open( + os.path.join(path_to_project, "Model", "base_model.py"), + "w", + encoding="utf-8", + ) as module_basemodel: + module_basemodel.write(temp_basemodel) + + +def create_module_basescreen() -> None: + with open( + os.path.join(path_to_project, "View", "base_screen.py"), + "w", + encoding="utf-8", + ) as base_screen: + base_screen.write(temp_base_screen) def create_controller( - name_database: str, - use_hotreload: str, - name_screen: str, - module_name: str, - path_to_project: str, + name_screen: str, module_name: str, use_hotreload: str, path_to_project: str ) -> None: - if name_database != "no": - database_controller = _database_controller - else: - database_controller = _without_database_controller name_view = ( f"View.{name_screen}.{module_name}.{name_screen}View" if use_hotreload == "yes" else f"{name_screen}View" ) - database_controller = database_controller.format( + code_controller = temp_code_controller.format( name_screen=name_screen, module_name=module_name, import_module="" @@ -738,184 +924,200 @@ def create_controller( f"# We have to manually reload the view module in order to apply the\n" f"# changes made to the code on a subsequent hot reload.\n" f"# If you no longer need a hot reload, you can delete this instruction.\n" - f"importlib.reload(View.{name_screen}.{module_name})" + f"importlib.reload(View.{name_screen}.{module_name})\n\n" if use_hotreload == "yes" else f"\nfrom View.{name_screen}.{module_name} import {name_screen}View", name_view=name_view, + get_view=f"View.{name_screen}.{module_name}" + if use_hotreload == "yes" + else f"{name_screen}View", ) - replace_in_file( - os.path.join(path_to_project, "Controller", "first_screen.py_tmp"), - (database_controller, name_view), + + path_to_controller = os.path.join(path_to_project, "Controller") + if not os.path.exists(path_to_controller): + os.mkdir(path_to_controller) + controller_module = os.path.join(path_to_project, "Controller", module_name) + with open(f"{controller_module}.py", "w", encoding="utf-8") as module: + module.write(code_controller) + + +def create_makefile() -> None: + makefile = temp_makefile.format(temp_makefile_files[:-2]) + with open( + os.path.join(path_to_project, "Makefile"), "w", encoding="utf-8" + ) as make_file: + make_file.write(makefile) + + +def create_makefile_data(name_screen: str, module_name: str) -> None: + global temp_makefile_files + + temp_makefile_files += ( + f" View/{name_screen}/{module_name}.py \\\n" ) - os.rename( - os.path.join(path_to_project, "Controller", "first_screen.py_tmp"), - os.path.join(path_to_project, "Controller", f"{module_name}.py_tmp"), + temp_makefile_files += ( + f" View/{name_screen}/{module_name}.kv \\\n" ) -def create_view( - name_database: str, - use_localization: str, - name_screen: str, - module_name: str, - path_to_project: str, -) -> None: - replace_in_file( - os.path.join(path_to_project, "View", "screens.py_tmp"), - ( - module_name, - f"{name_screen}Model", - module_name, - f"{name_screen}Controller", +def create_screens_data(name_screen: str, module_name: str) -> None: + global temp_screens_imports + global temp_screens_data + + temp_screens_imports += ( + f"from Model.{module_name} import {name_screen}Model\n" + f"from Controller.{module_name} import {name_screen}Controller\n" + ) + temp_screens_data += ( + '\n %s: {\n "model": %s,' + '\n "controller": %s,\n },\n' + % ( f'"{" ".join(module_name.split("_"))}"', f"{name_screen}Model", f"{name_screen}Controller", - ), - ) - replace_in_file( - os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"), - ( - f"{name_screen}View", - name_screen, - "app.switch_lang()" if use_localization == "yes" else "x", - 'app.translation._("To log in, enter your personal data:")' - if use_localization == "yes" - else '"To log in, enter your personal data:"', - 'app.translation._("Login")' - if use_localization == "yes" - else '"Login"', - 'app.translation._("Password")' - if use_localization == "yes" - else '"Password"', - 'app.translation._("LOGIN")' - if use_localization == "yes" - else '"LOGIN"', - ), - ) - replace_in_file( - os.path.join( - path_to_project, "View", "FirstScreen", "first_screen.py_tmp" - ), - ( - _database_view_import - if name_database != "no" - else _without_database_view_import, - f"{name_screen}View", - _database_view_methods if name_database != "no" else "", - _database_view_model_is_changed_method - if name_database != "no" - else "", - ), - ) - replace_in_file( - os.path.join(path_to_project, "View", "base_screen.py_tmp"), - ( - module_name, - f"{name_screen}Model", - module_name, - f"{name_screen}Controller", - module_name, - f"{name_screen}Model", - ), - ) - os.rename( - os.path.join(path_to_project, "View", "base_screen.py_tmp"), - os.path.join(path_to_project, "View", "base_screen.py"), - ) - os.rename( - os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"), - os.path.join( - path_to_project, "View", "FirstScreen", f"{module_name}.kv" - ), - ) - os.rename( - os.path.join( - path_to_project, "View", "FirstScreen", "first_screen.py_tmp" - ), - os.path.join( - path_to_project, "View", "FirstScreen", f"{module_name}.py_tmp" - ), - ) - os.rename( - os.path.join(path_to_project, "View", "FirstScreen"), - os.path.join(path_to_project, "View", name_screen), + ) ) -def create_requirements(name_database: str, path_to_project: str) -> None: +def create_module_screens() -> None: + path_to_module_screens = os.path.join(path_to_project, "View", "screens.py") + with open(path_to_module_screens, "w", encoding="utf-8") as module_screens: + module_screens.write( + "%s\nscreens = {%s}" % (temp_screens_imports, temp_screens_data) + ) + + +def create_common_responsive_module( + use_responsive: list, path_to_project: str +) -> None: + for name_screen in use_responsive: + path_to_init_common = os.path.join( + path_to_project, "View", name_screen, "components", "common" + ) + os.makedirs(path_to_init_common) + with open( + os.path.join(path_to_init_common, "__init__.py"), + "w", + encoding="utf-8", + ) as init_common_components: + init_common_components.write( + "# This directory is for common responsive design components\n" + ) + + +def create_view( + name_screen: str, + module_name: str, + use_responsive: list, + path_to_project: str, +) -> None: + path_to_view = os.path.join(path_to_project, "View", name_screen) + path_to_components = os.path.join(path_to_view, "components") + view_module = os.path.join(path_to_view, module_name) + os.makedirs(path_to_view) + os.makedirs(path_to_components) + + with open( + os.path.join(path_to_view, "__init__.py"), "w", encoding="utf-8" + ) as init_module: + init_module.write("") + with open(f"{view_module}.py", "w", encoding="utf-8") as view_file: + view_file.write( + temp_code_view.format(name_screen=name_screen) + if name_screen not in use_responsive + else temp_code_responsive_view.format(name_screen=name_screen) + ) + + if name_screen in use_responsive: + for name_platform in ["DesktopScreen", "MobileScreen", "TabletScreen"]: + path_to_init_components = os.path.join( + path_to_project, + "View", + name_screen, + "components", + "__init__.py", + ) + path_to_platforms = os.path.join( + path_to_project, "View", name_screen, "components", "platforms" + ) + path_to_platform = os.path.join(path_to_platforms, name_platform) + path_to_platform_components = os.path.join( + path_to_platform, "components" + ) + os.makedirs(path_to_platform_components) + shutil.copy( + os.path.join(path_to_view, "__init__.py"), + path_to_platform_components, + ) + shutil.copy( + os.path.join(path_to_view, "__init__.py"), path_to_platforms + ) + + name_platform_module = ( + f'{name_platform.split("Screen")[0].lower()}_screen' + ) + with open( + os.path.join(path_to_platform, f"{name_platform_module}.kv"), + "w", + encoding="utf-8", + ) as platform_rule: + platform_rule.write(f"<{name_platform}View>\n") + with open( + os.path.join(path_to_platform, f"{name_platform_module}.py"), + "w", + encoding="utf-8", + ) as platform_baseclass: + platform_baseclass.write( + temp_responsive_platform_baseclass.format(name_platform) + ) + + with open( + path_to_init_components, "w", encoding="utf-8" + ) as init_components: + init_components.write(temp_responsive_component_imports) + + with open(f"{view_module}.kv", "w", encoding="utf-8") as view_file: + view_file.write(f"<{name_screen}View>\n") + + if name_screen not in use_responsive: + shutil.copy( + os.path.join(path_to_view, "__init__.py"), path_to_components + ) + + +def create_package_utility() -> None: + path_to_utility = os.path.join(path_to_project, "Utility") + os.mkdir(path_to_utility) + + with open( + os.path.join(path_to_utility, "__init__.py"), "w", encoding="utf-8" + ) as init_module: + init_module.write("") + with open( + os.path.join(path_to_utility, "observer.py"), "w", encoding="utf-8" + ) as observer: + observer.write(temp_utility) + + +def create_requirements() -> None: with open( os.path.join(path_to_project, "requirements.txt"), "w", encoding="utf-8" ) as requirements: requirements.write( - _firebase_requirements - if name_database != "no" - else _without_firebase_requirements + firebase_requirements + if name_database == "firebase" + else without_firebase_requirements ) -def create_makefile( - path_to_project: str, project_name: str, module_name: str, name_screen: str -) -> None: - path_to_makefile_tmp = os.path.join(path_to_project, "Makefile.tmp") - replace_in_file( - path_to_makefile_tmp, - ( - name_screen, - module_name, - name_screen, - module_name, - project_name, - project_name, - ), - ) - os.rename(path_to_makefile_tmp, os.path.splitext(path_to_makefile_tmp)[0]) - os.chdir(path_to_project) - os.system("make po") - - -def create_mofile(path_to_project: str) -> None: - os.chdir(path_to_project) - os.system("make mo") - - -def create_virtual_environment( - python_version: str, path_to_project: str -) -> None: +def create_virtual_environment() -> None: os.system(f"{python_version} -m pip install virtualenv") os.system( f"virtualenv -p {python_version} {os.path.join(path_to_project, 'venv')}" ) -def localization_po_file(path_to_project: str) -> None: - path_to_file_po = os.path.join( - path_to_project, "data", "locales", "po", "ru.po" - ) - with open(path_to_file_po, "rt", encoding="utf-8") as file_po: - file_po_content = ( - file_po.read() - .replace( - 'msgid "To log in, enter your personal data:"\nmsgstr ""', - 'msgid "To log in, enter your personal data:"\nmsgstr "Для входа введите свои личные данные"', - ) - .replace( - 'msgid "Login"\nmsgstr ""', 'msgid "Login"\nmsgstr "Логин"' - ) - .replace( - 'msgid "Password"\nmsgstr ""', - 'msgid "Password"\nmsgstr "Пароль"', - ) - .replace( - 'msgid "LOGIN"\nmsgstr ""', 'msgid "LOGIN"\nmsgstr "ЛОГИН"' - ) - ) - with open(path_to_file_po, "wt", encoding="utf-8") as file_po: - file_po.write(file_po_content) - - -def install_requirements( - path_to_project: str, kivy_version: str, name_database: str -) -> None: +def install_requirements() -> None: python = os.path.join(path_to_project, "venv", "bin", "python3") if kivy_version == "master": if platform == "macosx": @@ -952,39 +1154,7 @@ def install_requirements( ) -def rename_ext_py_tmp_to_py(path_to_project: str) -> None: - for path_to_dir, dirs, files in os.walk(path_to_project): - for name_file in files: - if os.path.splitext(name_file)[1] == ".py_tmp": - os.rename( - os.path.join(path_to_dir, name_file), - os.path.join( - path_to_dir, f"{os.path.splitext(name_file)[0]}.py" - ), - ) - - -def move_init(path_to_project: str, name_screen: str) -> None: - path_to_init_file = __file__.replace("create_project", "__init__") - for name_dir in ("Controller", "Model", "Utility", "View"): - shutil.copy( - path_to_init_file, - os.path.join(path_to_project, name_dir, "__init__.py"), - ) - shutil.copy( - path_to_init_file, - os.path.join(path_to_project, "View", name_screen, "__init__.py"), - ) - path_to_components = os.path.join( - path_to_project, "View", name_screen, "components" - ) - os.mkdir(path_to_components) - shutil.copy( - path_to_init_file, os.path.join(path_to_components, "__init__.py") - ) - - -def check_databases(name_database: str, path_to_project: str) -> None: +def check_databases() -> None: databases = {"firebase": "restdb", "restdb": "firebase"} os.remove( os.path.join( @@ -1004,13 +1174,6 @@ def chek_camel_case_name_project(name_project) -> Union[bool, list]: return result -def replace_in_file(path_to_file: str, args) -> None: - with open(path_to_file, "rt", encoding="utf-8") as file_content: - new_file_content = file_content.read() % (args) - with open(path_to_file, "wt", encoding="utf-8") as original_file: - original_file.write(new_file_content) - - def create_argument_parser() -> ArgumentParserWithHelp: parser = ArgumentParserWithHelp( prog="create_project.py", @@ -1040,8 +1203,17 @@ def create_argument_parser() -> ArgumentParserWithHelp: ) parser.add_argument( "--name_screen", - default="MainScreen", - help="the name of the class wich be used when creating the project pattern.", + nargs="*", + type=str, + default=["MainScreen"], + help="the name/names of the class which be used when creating the project pattern.", + ) + parser.add_argument( + "--use_responsive", + nargs="*", + type=str, + default=[], + help="the name/names of the views to be used by the responsive UI.", ) parser.add_argument( "--name_database", diff --git a/sbapp/kivymd/tools/release/git_commands.py b/sbapp/kivymd/tools/release/git_commands.py index e194765..fc82f44 100644 --- a/sbapp/kivymd/tools/release/git_commands.py +++ b/sbapp/kivymd/tools/release/git_commands.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2021 Artem Bulgakov +# Copyright (c) 2019-2022 Artem Bulgakov # # This file is distributed under the terms of the same license, # as the Kivy framework. diff --git a/sbapp/kivymd/tools/release/make_release.py b/sbapp/kivymd/tools/release/make_release.py index fbb616a..f321a2d 100644 --- a/sbapp/kivymd/tools/release/make_release.py +++ b/sbapp/kivymd/tools/release/make_release.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2021 Artem Bulgakov +# Copyright (c) 2019-2022 Artem Bulgakov # # This file is distributed under the terms of the same license, # as the Kivy framework. diff --git a/sbapp/kivymd/tools/release/update_icons.py b/sbapp/kivymd/tools/release/update_icons.py index 2e24148..13fd43d 100644 --- a/sbapp/kivymd/tools/release/update_icons.py +++ b/sbapp/kivymd/tools/release/update_icons.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2021 Artem Bulgakov +# Copyright (c) 2019-2022 Artem Bulgakov # # This file is distributed under the terms of the same license, # as the Kivy framework. diff --git a/sbapp/kivymd/uix/anchorlayout.py b/sbapp/kivymd/uix/anchorlayout.py index c50fa0c..4e2be4a 100644 --- a/sbapp/kivymd/uix/anchorlayout.py +++ b/sbapp/kivymd/uix/anchorlayout.py @@ -10,7 +10,7 @@ with some widget properties. For example: AnchorLayout ------------ -.. code-block:: +.. code-block:: kv AnchorLayout: canvas: @@ -20,65 +20,13 @@ AnchorLayout pos: self.pos size: self.size -AnchorLayout ------------- - -.. code-block:: - - MDBoxLayout: - md_bg_color: app.theme_cls.primary_color - -Available options are: ----------------------- - -- adaptive_height_ -- adaptive_width_ -- adaptive_size_ - -.. adaptive_height: -adaptive_height ---------------- - -.. code-block:: kv - - adaptive_height: True - -Equivalent - -.. code-block:: kv - - size_hint_y: None - height: self.minimum_height - -.. adaptive_width: -adaptive_width +MDAnchorLayout -------------- .. code-block:: kv - adaptive_width: True - -Equivalent - -.. code-block:: kv - - size_hint_x: None - height: self.minimum_width - -.. adaptive_size: -adaptive_size -------------- - -.. code-block:: kv - - adaptive_size: True - -Equivalent - -.. code-block:: kv - - size_hint: None, None - size: self.minimum_size + MDAnchorLayout: + md_bg_color: app.theme_cls.primary_color """ __all__ = ("MDAnchorLayout",) @@ -86,7 +34,11 @@ __all__ = ("MDAnchorLayout",) from kivy.uix.anchorlayout import AnchorLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDAnchorLayout(AnchorLayout, MDAdaptiveWidget): - pass +class MDAnchorLayout(DeclarativeBehavior, AnchorLayout, MDAdaptiveWidget): + """ + Anchor layout class. For more information, see in the + :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/backdrop/backdrop.kv b/sbapp/kivymd/uix/backdrop/backdrop.kv index 372c754..2f56d5b 100644 --- a/sbapp/kivymd/uix/backdrop/backdrop.kv +++ b/sbapp/kivymd/uix/backdrop/backdrop.kv @@ -35,8 +35,8 @@ if not root.front_layer_color \ else root.front_layer_color radius: - [root.radius_left, root.radius_left, - root.radius_right, root.radius_right] + [root.radius_left, root.radius_right, + 0, 0] OneLineListItem: id: header_button diff --git a/sbapp/kivymd/uix/backdrop/backdrop.py b/sbapp/kivymd/uix/backdrop/backdrop.py index 77a3028..604ab13 100644 --- a/sbapp/kivymd/uix/backdrop/backdrop.py +++ b/sbapp/kivymd/uix/backdrop/backdrop.py @@ -31,84 +31,147 @@ Usage Example ------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV styles - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp + .. code-block:: python - # Your layouts. - Builder.load_string( - ''' - #:import Window kivy.core.window.Window - #:import IconLeftWidget kivymd.uix.list.IconLeftWidget + from kivy.lang import Builder + + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp + + # Your layouts. + Builder.load_string( + ''' + #:import os os + #:import Window kivy.core.window.Window + #:import IconLeftWidget kivymd.uix.list.IconLeftWidget + #:import images_path kivymd.images_path - - icon: "android" + + icon: "android" - IconLeftWidget: - icon: root.icon + IconLeftWidget: + icon: root.icon - - backdrop: None - text: "Lower the front layer" - secondary_text: " by 50 %" - icon: "transfer-down" - on_press: root.backdrop.open(-Window.height / 2) - pos_hint: {"top": 1} - _no_ripple_effect: True + + backdrop: None + text: "Lower the front layer" + secondary_text: " by 50 %" + icon: "transfer-down" + on_press: root.backdrop.open(-Window.height / 2) + pos_hint: {"top": 1} + _no_ripple_effect: True - - size_hint: .8, .8 - source: "data/logo/kivy-icon-512.png" - pos_hint: {"center_x": .5, "center_y": .6} - ''' - ) + + size_hint: .8, .8 + source: os.path.join(images_path, "logo", "kivymd-icon-512.png") + pos_hint: {"center_x": .5, "center_y": .6} + ''' + ) - # Usage example of MDBackdrop. - Builder.load_string( - ''' - + # Usage example of MDBackdrop. + Builder.load_string( + ''' + - MDBackdrop: - id: backdrop - left_action_items: [['menu', lambda x: self.open()]] - title: "Example Backdrop" - radius_left: "25dp" - radius_right: "0dp" - header_text: "Menu:" + MDBackdrop: + id: backdrop + left_action_items: [['menu', lambda x: self.open()]] + title: "Example Backdrop" + radius_left: "25dp" + radius_right: "0dp" + header_text: "Menu:" - MDBackdropBackLayer: - MyBackdropBackLayer: - id: backlayer + MDBackdropBackLayer: + MyBackdropBackLayer: + id: backlayer - MDBackdropFrontLayer: - MyBackdropFrontLayer: - backdrop: backdrop - ''' - ) + MDBackdropFrontLayer: + MyBackdropFrontLayer: + backdrop: backdrop + ''' + ) - class ExampleBackdrop(MDScreen): - pass + class ExampleBackdrop(MDScreen): + pass - class TestBackdrop(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def build(self): - return ExampleBackdrop() + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ExampleBackdrop() - TestBackdrop().run() + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + import os + + from kivy.core.window import Window + from kivy.uix.image import Image + + from kivymd import images_path + from kivymd.uix.backdrop import MDBackdrop + from kivymd.uix.backdrop.backdrop import ( + MDBackdropBackLayer, MDBackdropFrontLayer + ) + from kivymd.uix.list import TwoLineAvatarListItem, IconLeftWidget + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + + return ( + MDScreen( + MDBackdrop( + MDBackdropBackLayer( + Image( + size_hint=(0.8, 0.8), + source=os.path.join(images_path, "logo", "kivymd-icon-512.png"), + pos_hint={"center_x": 0.5, "center_y": 0.6}, + ) + ), + MDBackdropFrontLayer( + TwoLineAvatarListItem( + IconLeftWidget(icon="transfer-down"), + text="Lower the front layer", + secondary_text=" by 50 %", + on_press=self.backdrop_open_by_50_percent, + pos_hint={"top": 1}, + _no_ripple_effect=True, + ), + ), + id="backdrop", + title="Example Backdrop", + radius_left="25dp", + radius_right="0dp", + header_text="Menu:", + ) + ) + ) + + def backdrop_open_by_50_percent(self, *args): + self.root.ids.backdrop.open(-Window.height / 2) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif - :width: 280 px :align: center .. Note:: `See full example `_ @@ -139,7 +202,7 @@ from kivy.uix.boxlayout import BoxLayout from kivymd import uix_path from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior +from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.card import MDCard from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.toolbar.toolbar import ActionTopAppBarButton, MDTopAppBar @@ -151,7 +214,7 @@ with open( Builder.load_string(kv_file.read()) -class MDBackdrop(ThemableBehavior, MDFloatLayout): +class MDBackdrop(MDFloatLayout, ThemableBehavior): """ :Events: :attr:`on_open` @@ -167,6 +230,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): .. versionadded:: 1.0.0 + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-anchor-title.png + :align: center + :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty` and defaults to `'left'`. """ @@ -175,6 +241,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): """ Padding for contents of the front layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-padding.png + :align: center + :attr:`padding` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0, 0, 0]`. """ @@ -210,6 +279,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): """ Background color of back layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-back-layer-color.png + :align: center + :attr:`back_layer_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ @@ -218,6 +290,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): """ Background color of front layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-front-layer-color.png + :align: center + :attr:`front_layer_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ @@ -227,6 +302,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): The value of the rounding radius of the upper left corner of the front layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-radius-left.png + :align: center + :attr:`radius_left` is an :class:`~kivy.properties.NumericProperty` and defaults to `16dp`. """ @@ -244,6 +322,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): """ Whether to use a header above the contents of the front layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header.png + :align: center + :attr:`header` is an :class:`~kivy.properties.BooleanProperty` and defaults to `True`. """ @@ -252,6 +333,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): """ Text of header. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header-text.png + :align: center + :attr:`header_text` is an :class:`~kivy.properties.StringProperty` and defaults to `'Header'`. """ @@ -261,6 +345,9 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): The name of the icon that will be installed on the toolbar on the left when opening the front layer. + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-close-icon.png + :align: center + :attr:`close_icon` is an :class:`~kivy.properties.StringProperty` and defaults to `'close'`. """ @@ -310,8 +397,8 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): _open_icon = "" _front_layer_open = False - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_open") self.register_event_type("on_close") Clock.schedule_once( @@ -332,8 +419,11 @@ class MDBackdrop(ThemableBehavior, MDFloatLayout): self._open_icon = self.left_action_items[0][0] def on_header(self, instance_backdrop, value: bool) -> None: - if not value: - self.ids._front_layer.remove_widget(self.ids.header_button) + def on_header(*args): + if not value: + self.ids._front_layer.remove_widget(self.ids.header_button) + + Clock.schedule_once(on_header) def open(self, open_up_to: int = 0) -> None: """ @@ -425,11 +515,11 @@ class MDBackdropToolbar(MDTopAppBar): """Implements a toolbar for back content.""" -class MDBackdropFrontLayer(BoxLayout): +class MDBackdropFrontLayer(MDBoxLayout): """Container for front content.""" -class MDBackdropBackLayer(BoxLayout): +class MDBackdropBackLayer(MDBoxLayout): """Container for back content.""" @@ -437,5 +527,5 @@ class _BackLayer(BoxLayout): pass -class _FrontLayer(MDCard, FakeRectangularElevationBehavior): +class _FrontLayer(MDCard): pass diff --git a/sbapp/kivymd/uix/banner/banner.py b/sbapp/kivymd/uix/banner/banner.py index 7feb5c9..fb87221 100644 --- a/sbapp/kivymd/uix/banner/banner.py +++ b/sbapp/kivymd/uix/banner/banner.py @@ -35,7 +35,7 @@ Usage MDTopAppBar: id: toolbar title: "Example Banners" - elevation: 10 + elevation: 4 pos_hint: {'top': 1} MDBoxLayout: @@ -157,7 +157,6 @@ from kivy.properties import ( from kivy.uix.widget import Widget from kivymd import uix_path -from kivymd.uix.behaviors import FakeRectangularElevationBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.button import MDFlatButton from kivymd.uix.card import MDCard @@ -177,7 +176,7 @@ with open( Builder.load_string(kv_file.read()) -class MDBanner(MDCard, FakeRectangularElevationBehavior): +class MDBanner(MDCard): vertical_pad = NumericProperty(dp(68)) """ Indent the banner at the top of the screen. diff --git a/sbapp/kivymd/uix/behaviors/__init__.py b/sbapp/kivymd/uix/behaviors/__init__.py index 639acff..e89ed10 100755 --- a/sbapp/kivymd/uix/behaviors/__init__.py +++ b/sbapp/kivymd/uix/behaviors/__init__.py @@ -5,22 +5,26 @@ Behaviors Modules and classes implementing various behaviors for buttons etc. """ -# flake8: NOQA -from .hover_behavior import HoverBehavior # isort:skip from .backgroundcolor_behavior import ( BackgroundColorBehavior, SpecificBackgroundColorBehavior, ) + +# flake8: NOQA +from .declarative_behavior import DeclarativeBehavior from .elevation import ( CircularElevationBehavior, CommonElevationBehavior, FakeCircularElevationBehavior, FakeRectangularElevationBehavior, - ObservableShadow, RectangularElevationBehavior, RoundedRectangularElevationBehavior, ) -from .focus_behavior import FocusBehavior from .magic_behavior import MagicBehavior from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior +from .rotate_behavior import RotateBehavior +from .scale_behavior import ScaleBehavior +from .stencil_behavior import StencilBehavior from .touch_behavior import TouchBehavior + +from .hover_behavior import HoverBehavior # isort:skip diff --git a/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py b/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py index d96fd60..4c4649b 100755 --- a/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py +++ b/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py @@ -7,8 +7,9 @@ Behaviors/Background Color __all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior") -from typing import List +from typing import List, Union +from kivy.animation import Animation from kivy.lang import Builder from kivy.properties import ( ColorProperty, @@ -24,8 +25,6 @@ from kivy.utils import get_color_from_hex from kivymd.color_definitions import hue, palette, text_colors from kivymd.theming import ThemeManager -from .elevation import CommonElevationBehavior - Builder.load_string( """ #:import RelativeLayout kivy.uix.relativelayout.RelativeLayout @@ -38,7 +37,7 @@ Builder.load_string( angle: self.angle origin: self._background_origin Color: - rgba: self.md_bg_color + rgba: self._md_bg_color RoundedRectangle: group: "Background_instruction" size: self.size @@ -67,7 +66,7 @@ Builder.load_string( ) -class BackgroundColorBehavior(CommonElevationBehavior): +class BackgroundColorBehavior: background = StringProperty() """ Background image path. @@ -153,15 +152,26 @@ class BackgroundColorBehavior(CommonElevationBehavior): _background_x = NumericProperty(0) _background_y = NumericProperty(0) - _background_origin = ReferenceListProperty( - _background_x, - _background_y, - ) + _background_origin = ReferenceListProperty(_background_x, _background_y) + _md_bg_color = ColorProperty([0, 0, 0, 0]) def __init__(self, **kwarg): super().__init__(**kwarg) self.bind(pos=self.update_background_origin) + def on_md_bg_color(self, instance_md_widget, color: Union[list, str]): + if ( + hasattr(self, "theme_cls") + and self.theme_cls.theme_style_switch_animation + ): + Animation( + _md_bg_color=color, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self._md_bg_color = color + def update_background_origin( self, instance_md_widget, pos: List[float] ) -> None: @@ -206,12 +216,14 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior): super().__init__(**kwargs) if hasattr(self, "theme_cls"): self.theme_cls.bind( - primary_palette=self._update_specific_text_color + primary_palette=self._update_specific_text_color, + accent_palette=self._update_specific_text_color, + theme_style=self._update_specific_text_color, ) - self.theme_cls.bind(accent_palette=self._update_specific_text_color) - self.theme_cls.bind(theme_style=self._update_specific_text_color) - self.bind(background_hue=self._update_specific_text_color) - self.bind(background_palette=self._update_specific_text_color) + self.bind( + background_hue=self._update_specific_text_color, + background_palette=self._update_specific_text_color, + ) self._update_specific_text_color(None, None) def _update_specific_text_color( @@ -234,5 +246,17 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior): secondary_color[3] = 0.54 else: secondary_color[3] = 0.7 - self.specific_text_color = color - self.specific_secondary_text_color = secondary_color + + if ( + hasattr(self, "theme_cls") + and self.theme_cls.theme_style_switch_animation + ): + Animation( + specific_text_color=color, + specific_secondary_text_color=secondary_color, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self.specific_text_color = color + self.specific_secondary_text_color = secondary_color diff --git a/sbapp/kivymd/uix/behaviors/declarative_behavior.py b/sbapp/kivymd/uix/behaviors/declarative_behavior.py new file mode 100644 index 0000000..0fb5eb7 --- /dev/null +++ b/sbapp/kivymd/uix/behaviors/declarative_behavior.py @@ -0,0 +1,317 @@ +""" +Behaviors/Declarative +===================== + +.. versionadded:: 1.0.0 + +.. raw:: html + +

+ +As you already know, the Kivy framework provides the best/simplest/modern +UI creation tool that allows you to separate the logic of your application +from the description of the properties of widgets/GUI components. +This tool is named `KV Language `_. + +But in addition to creating a user interface using the KV Language Kivy allows +you to create user interface elements directly in the Python code. +And if you've ever created a user interface in Python code, you know how ugly +it looks. Even in the simplest user interface design, which was created using +Python code it is impossible to trace the widget tree, because in Python code +you build the user interface in an imperative style. + +Imperative style +---------------- + +.. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + screen = MDScreen() + bottom_navigation = MDBottomNavigation( + panel_color="#eeeaea", + selected_color_background="#97ecf8", + text_color_active="white", + ) + + data = { + "screen 1": {"text": "Mail", "icon": "gmail"}, + "screen 2": {"text": "Discord", "icon": "discord"}, + "screen 3": {"text": "LinkedIN", "icon": "linkedin"}, + } + for key in data.keys(): + text = data[key]["text"] + navigation_item = MDBottomNavigationItem( + name=key, text=text, icon=data[key]["icon"] + ) + navigation_item.add_widget(MDLabel(text=text, halign="center")) + bottom_navigation.add_widget(navigation_item) + + screen.add_widget(bottom_navigation) + return screen + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png + :align: center + +Take a look at the above code example. This is a very simple UI. But looking +at this code, you will not be able to figure the widget tree and understand +which UI this code implements. This is named imperative programming style, +which is used in Kivy. + +Now let's see how the same code is implemented using the KV language, +which uses a declarative style of describing widget properties. + +Declarative style with KV language +---------------------------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + + class Test(MDApp): + def build(self): + return Builder.load_string( + ''' + MDScreen: + + MDBottomNavigation: + panel_color: "#eeeaea" + selected_color_background: "#97ecf8" + text_color_active: "white" + + MDBottomNavigationItem: + name: "screen 1" + text: "Mail" + icon: "gmail" + + MDLabel: + text: "Mail" + halign: "center" + + MDBottomNavigationItem: + name: "screen 2" + text: "Discord" + icon: "discord" + + MDLabel: + text: "Discord" + halign: "center" + + MDBottomNavigationItem: + name: "screen 3" + text: "LinkedIN" + icon: "linkedin" + + MDLabel: + text: "LinkedIN" + halign: "center" + ''' + ) + + + Test().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png + :align: center + +Looking at this code, we can now clearly see the widget tree and their properties. +We can quickly navigate through the components of the screen and quickly +change/add new properties/widgets. This is named declarative UI creation style. + +But now the KivyMD library allows you to write Python code in a declarative style. +Just as it is implemented in Flutter/Jetpack Compose/SwiftUI. + +Declarative style with Python code +---------------------------------- + +.. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + return ( + MDScreen( + MDBottomNavigation( + MDBottomNavigationItem( + MDLabel( + text="Mail", + halign="center", + ), + name="screen 1", + text="Mail", + icon="gmail", + ), + MDBottomNavigationItem( + MDLabel( + text="Discord", + halign="center", + ), + name="screen 2", + text="Discord", + icon="discord", + ), + MDBottomNavigationItem( + MDLabel( + text="LinkedIN", + halign="center", + ), + name="screen 3", + text="LinkedIN", + icon="linkedin", + ), + panel_color="#eeeaea", + selected_color_background="#97ecf8", + text_color_active="white", + ) + ) + ) + + + Example().run() + +.. note:: The KivyMD library does not support creating Kivy widgets in Python + code in a declarative style. + +But you can still use the declarative style of creating Kivy widgets in Python code. +To do this, you need to create a new class that will be inherited from the Kivy +widget and the :class:`~DeclarativeBehavior` class: + +.. code-block:: python + + from kivy.uix.boxlayout import BoxLayout + from kivy.uix.button import Button + + from kivymd.app import MDApp + from kivymd.uix.behaviors import DeclarativeBehavior + + + class DeclarativeStyleBoxLayout(DeclarativeBehavior, BoxLayout): + pass + + + class Example(MDApp): + def build(self): + return ( + DeclarativeStyleBoxLayout( + Button(), + Button(), + orientation="vertical", + ) + ) + + + Example().run() + +Get objects by identifiers +-------------------------- + +In the declarative style in Python code, the ids parameter of the specified +widget will return only the id of the child widget/container, ignoring other ids. +Therefore, to get objects by identifiers in declarative style in Python code, +you must specify all the container ids in which the widget is nested until you +get to the desired id: + +.. code-block:: + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDRaisedButton + from kivymd.uix.floatlayout import MDFloatLayout + + + class Example(MDApp): + def build(self): + return ( + MDBoxLayout( + MDFloatLayout( + MDRaisedButton( + id="button_1", + text="Button 1", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), + id="box_container_1", + ), + MDBoxLayout( + MDFloatLayout( + MDRaisedButton( + id="button_2", + text="Button 2", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), + id="float_container", + ), + id="box_container_2", + ) + ) + ) + + def on_start(self): + # { + # 'box_container_1': , + # 'box_container_2': + # } + print(self.root.ids) + + # + print(self.root.ids.box_container_2.ids.float_container.ids.button_2) + + + Example().run() + +Yes, this is not a very good solution, but I think it will be fixed soon. + +.. warning:: Declarative programming style in Python code in the KivyMD library + is an experimental feature. Therefore, if you receive errors, do not hesitate + to create new issue in the KivyMD repository. +""" + +from kivy.properties import StringProperty +from kivy.uix.widget import Widget + + +class DeclarativeBehavior: + """ + Implements the creation and addition of child widgets as declarative + programming style. + """ + + id = StringProperty() + """ + Widget ID. + + :attr:`id` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(**kwargs) + + for child in args: + if issubclass(child.__class__, Widget): + self.add_widget(child) + if hasattr(child, "id") and child.id: + self.ids[child.id] = child diff --git a/sbapp/kivymd/uix/behaviors/elevation.py b/sbapp/kivymd/uix/behaviors/elevation.py index 584c82b..5b23dba 100755 --- a/sbapp/kivymd/uix/behaviors/elevation.py +++ b/sbapp/kivymd/uix/behaviors/elevation.py @@ -11,419 +11,12 @@ Behaviors/Elevation .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png :align: center -There are 5 classes in KivyMD that can simulate shadow: +To create an elevation effect, use the :class:`~CommonElevationBehavior` class. +For example, let's create a button with a rectangular elevation effect: - #. :class:`~FakeRectangularElevationBehavior` - #. :class:`~FakeCircularElevationBehavior` +.. tabs:: - #. :class:`~RectangularElevationBehavior` - #. :class:`~CircularElevationBehavior` - #. :class:`~RoundedRectangularElevationBehavior` - -By default, KivyMD widgets use the elevation behavior implemented in classes -:class:`~FakeRectangularElevationBehavior` and :class:`~FakeCircularElevationBehavior` -for cast shadows. These classes use the old method of rendering shadows and it -doesn't look very aesthetically pleasing. Shadows are harsh, no softness: - -The :class:`~RectangularElevationBehavior`, :class:`~CircularElevationBehavior`, -:class:`~RoundedRectangularElevationBehavior` classes use the new shadow -rendering algorithm, based on textures creation using the `Pillow` library. -It looks very aesthetically pleasing and beautiful. - -.. warning:: Remember that :class:`~RectangularElevationBehavior`, - :class:`~CircularElevationBehavior`, :class:`~RoundedRectangularElevationBehavior` - classes require a lot of resources from the device on which your application will run, - so you should not use these classes on mobile devices. - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.widget import Widget - - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - from kivymd.uix.behaviors import RectangularElevationBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - - adaptive_size: True - orientation: "vertical" - spacing: "36dp" - - - - size_hint: None, None - size: 100, 100 - md_bg_color: 0, 0, 1, 1 - elevation: 36 - pos_hint: {'center_x': .5} - - - MDFloatLayout: - - MDBoxLayout: - adaptive_size: True - pos_hint: {'center_x': .5, 'center_y': .5} - spacing: "56dp" - - Box: - - MDLabel: - text: "Deprecated shadow rendering" - adaptive_size: True - - DeprecatedShadowWidget: - - MDLabel: - text: "Doesn't require a lot of resources" - adaptive_size: True - - Box: - - MDLabel: - text: "New shadow rendering" - adaptive_size: True - - NewShadowWidget: - - MDLabel: - text: "It takes a lot of resources" - adaptive_size: True - ''' - - - class BaseShadowWidget(Widget): - pass - - - class DeprecatedShadowWidget(MDCard, BaseShadowWidget): - '''Deprecated shadow rendering. Doesn't require a lot of resources.''' - - - class NewShadowWidget(RectangularElevationBehavior, BaseShadowWidget, MDBoxLayout): - '''New shadow rendering. It takes a lot of resources.''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-differential.png - :align: center - - -For example, let's create an button with a rectangular elevation effect: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.uix.behaviors import ( - RectangularRippleBehavior, - BackgroundColorBehavior, - FakeRectangularElevationBehavior, - ) - - KV = ''' - : - size_hint: None, None - size: "250dp", "50dp" - - - MDScreen: - - # With elevation effect - RectangularElevationButton: - pos_hint: {"center_x": .5, "center_y": .6} - elevation: 18 - - # Without elevation effect - RectangularElevationButton: - pos_hint: {"center_x": .5, "center_y": .4} - ''' - - - class RectangularElevationButton( - RectangularRippleBehavior, - FakeRectangularElevationBehavior, - ButtonBehavior, - BackgroundColorBehavior, - ): - md_bg_color = [0, 0, 1, 1] - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif - :align: center - -Similarly, create a circular button: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.app import MDApp - from kivymd.uix.behaviors import ( - CircularRippleBehavior, - FakeCircularElevationBehavior, - ) - - KV = ''' - : - size_hint: None, None - size: "100dp", "100dp" - radius: self.size[0] / 2 - md_bg_color: 0, 0, 1, 1 - - MDIcon: - icon: "hand-heart" - halign: "center" - valign: "center" - size: root.size - pos: root.pos - font_size: root.size[0] * .6 - theme_text_color: "Custom" - text_color: [1] * 4 - - - MDScreen: - - CircularElevationButton: - pos_hint: {"center_x": .5, "center_y": .6} - elevation: 24 - ''' - - - class CircularElevationButton( - FakeCircularElevationBehavior, - CircularRippleBehavior, - ButtonBehavior, - MDBoxLayout, - ): - pass - - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-fake-elevation.png - :align: center - -Animating the elevation ------------------------ - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.properties import ObjectProperty - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.theming import ThemableBehavior - from kivymd.uix.behaviors import FakeRectangularElevationBehavior, RectangularRippleBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - MDFloatLayout: - - ElevatedWidget: - pos_hint: {'center_x': .5, 'center_y': .5} - size_hint: None, None - size: 100, 100 - md_bg_color: 0, 0, 1, 1 - ''' - - - class ElevatedWidget( - ThemableBehavior, - FakeRectangularElevationBehavior, - RectangularRippleBehavior, - ButtonBehavior, - MDBoxLayout, - ): - shadow_animation = ObjectProperty() - - def on_press(self, *args): - if self.shadow_animation: - Animation.cancel_all(self, "_elevation") - self.shadow_animation = Animation(_elevation=self.elevation + 10, d=0.4) - self.shadow_animation.start(self) - - def on_release(self, *args): - if self.shadow_animation: - Animation.cancel_all(self, "_elevation") - self.shadow_animation = Animation(_elevation=self.elevation, d=0.1) - self.shadow_animation.start(self) - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif - :align: center - -Lighting position ------------------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.behaviors import RectangularElevationBehavior - - KV = ''' - MDScreen: - - ShadowCard: - pos_hint: {'center_x': .5, 'center_y': .5} - size_hint: None, None - size: 100, 100 - shadow_pos: -10 + slider.value, -10 + slider.value - elevation: 24 - md_bg_color: 1, 1, 1, 1 - - MDSlider: - id: slider - max: 20 - size_hint_x: .6 - pos_hint: {'center_x': .5, 'center_y': .3} - ''' - - - class ShadowCard(RectangularElevationBehavior, MDBoxLayout): - pass - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-pos.gif - :align: center -""" - -__all__ = ( - "CommonElevationBehavior", - "RectangularElevationBehavior", - "CircularElevationBehavior", - "RoundedRectangularElevationBehavior", - "ObservableShadow", - "FakeRectangularElevationBehavior", - "FakeCircularElevationBehavior", -) - -from io import BytesIO -from weakref import WeakMethod, ref - -from kivy import Logger -from kivy.clock import Clock -from kivy.core.image import Image as CoreImage -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - BoundedNumericProperty, - ListProperty, - NumericProperty, - ObjectProperty, - ReferenceListProperty, - StringProperty, - VariableListProperty, -) -from kivy.uix.widget import Widget -from PIL import Image, ImageDraw, ImageFilter - -from kivymd.app import MDApp - -Builder.load_string( - """ -#:import InstructionGroup kivy.graphics.instructions.InstructionGroup - - - - canvas.before: - # SOFT SHADOW - PushMatrix - Rotate: - angle: self.angle - origin: self._shadow_origin - Color: - group: "soft_shadow" - rgba: root.soft_shadow_cl - Rectangle: - group: "soft_shadow" - texture: self._soft_shadow_texture - size: self.soft_shadow_size - pos: self.soft_shadow_pos - PopMatrix - - # HARD SHADOW - PushMatrix - Rotate: - angle: self.angle - origin: self.center - Color: - group: "hard_shadow" - rgba: root.hard_shadow_cl - Rectangle: - group: "hard_shadow" - texture: self.hard_shadow_texture - size: self.hard_shadow_size - pos: self.hard_shadow_pos - PopMatrix - Color: - group: "shadow" - a: 1 -""", - filename="CommonElevationBehavior.kv", -) - - -class CommonElevationBehavior(Widget): - """Common base class for rectangular and circular elevation behavior.""" - - elevation = BoundedNumericProperty(0, min=0, errorvalue=0) - """ - Elevation of the widget. - - .. note:: - Although, this value does not represent the current elevation of the - widget. :attr:`~CommonElevationBehavior._elevation` can be used to - animate the current elevation and come back using the - :attr:`~CommonElevationBehavior.elevation` property directly. - - For example: + .. tab:: Declarative style with KV .. code-block:: python @@ -431,52 +24,41 @@ class CommonElevationBehavior(Widget): from kivy.uix.behaviors import ButtonBehavior from kivymd.app import MDApp - from kivymd.uix.behaviors import CircularElevationBehavior, CircularRippleBehavior - from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.behaviors import ( + RectangularRippleBehavior, + BackgroundColorBehavior, + CommonElevationBehavior, + ) KV = ''' - #:import Animation kivy.animation.Animation + + size_hint: None, None + size: "250dp", "50dp" - - size_hint: [None, None] - elevation: 6 - animation_: None - md_bg_color: [1] * 4 - on_size: - self.radius = [self.height / 2] * 4 - on_press: - if self.animation_: \ - self.animation_.cancel(self); \ - self.animation_ = Animation(_elevation=self.elevation + 6, d=0.08); \ - self.animation_.start(self) - on_release: - if self.animation_: \ - self.animation_.cancel(self); \ - self.animation_ = Animation(_elevation = self.elevation, d=0.08); \ - self.animation_.start(self) + MDScreen: - MDFloatLayout: + # With elevation effect + RectangularElevationButton: + pos_hint: {"center_x": .5, "center_y": .6} + elevation: 4.5 + shadow_offset: 0, 6 - WidgetWithShadow: - size: [root.size[1] / 2] * 2 - pos_hint: {"center": [0.5, 0.5]} + # Without elevation effect + RectangularElevationButton: + pos_hint: {"center_x": .5, "center_y": .4} ''' - class WidgetWithShadow( - CircularElevationBehavior, - CircularRippleBehavior, + class RectangularElevationButton( + RectangularRippleBehavior, + CommonElevationBehavior, ButtonBehavior, - MDBoxLayout, + BackgroundColorBehavior, ): def __init__(self, **kwargs): - # always set the elevation before the super().__init__ call - # self.elevation = 6 super().__init__(**kwargs) - - def on_size(self, *args): - self.radius = [self.size[0] / 2] + self.md_bg_color = "red" class Example(MDApp): @@ -486,990 +68,831 @@ class CommonElevationBehavior(Widget): Example().run() + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import ( + RectangularRippleBehavior, + BackgroundColorBehavior, + CommonElevationBehavior, + ) + from kivymd.uix.screen import MDScreen + + + class RectangularElevationButton( + RectangularRippleBehavior, + CommonElevationBehavior, + ButtonBehavior, + BackgroundColorBehavior, + ): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.md_bg_color = "red" + self.size_hint = (None, None) + self.size = ("250dp", "50dp") + + + class Example(MDApp): + def build(self): + return ( + MDScreen( + RectangularElevationButton( + pos_hint={"center_x": .5, "center_y": .6}, + elevation=4.5, + shadow_offset=(0, 6), + ), + RectangularElevationButton( + pos_hint={"center_x": .5, "center_y": .4}, + ), + ) + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.png + :align: center + +.. warning:: + + If before the KivyMD 1.1.0 library version you used the elevation property + with an average value of `12` for the shadow, then starting with the KivyMD + 1.1.0 library version, the average value of the elevation property will be + somewhere `4`. + +Similarly, create a circular button: + +.. tabs:: + + .. tab:: Declarative style with KV + + .. code-block:: python + + from kivy.lang import Builder + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior + from kivymd.uix.floatlayout import MDFloatLayout + + KV = ''' + + size_hint: None, None + size: "100dp", "100dp" + radius: self.size[0] / 2 + shadow_radius: self.radius[0] + md_bg_color: "red" + + MDIcon: + icon: "hand-heart" + halign: "center" + valign: "center" + pos_hint: {"center_x": .5, "center_y": .5} + size: root.size + pos: root.pos + font_size: root.size[0] * .6 + theme_text_color: "Custom" + text_color: "white" + + + MDScreen: + + CircularElevationButton: + pos_hint: {"center_x": .5, "center_y": .6} + elevation: 4 + ''' + + + class CircularElevationButton( + CommonElevationBehavior, + CircularRippleBehavior, + ButtonBehavior, + MDFloatLayout, + ): + pass + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.metrics import dp + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.label import MDIcon + from kivymd.uix.screen import MDScreen + + + class CircularElevationButton( + CommonElevationBehavior, + CircularRippleBehavior, + ButtonBehavior, + MDFloatLayout, + ): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.size_hint = (None, None) + self.size = (dp(100), dp(100)) + self.radius = dp(100) / 2 + self.shadow_radius = dp(100) / 2 + self.md_bg_color = "red" + self.add_widget( + MDIcon( + icon="hand-heart", + halign="center", + valign="center", + pos_hint={"center_x": .5, "center_y": .5}, + size=self.size, + theme_text_color="Custom", + text_color="white", + font_size=self.size[0] * 0.6, + ) + ) + + + class Example(MDApp): + def build(self): + return ( + MDScreen( + CircularElevationButton( + pos_hint={"center_x": .5, "center_y": .5}, + elevation=4, + ) + ) + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.png + :align: center + +Animating the elevation +----------------------- + +.. tabs:: + + .. tab:: Declarative style with KV + + .. code-block:: python + + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior + from kivymd.uix.widget import MDWidget + + KV = ''' + MDScreen: + + ElevatedWidget: + pos_hint: {'center_x': .5, 'center_y': .5} + size_hint: None, None + size: 100, 100 + md_bg_color: 0, 0, 1, 1 + elevation: 4 + radius: 18 + ''' + + + class ElevatedWidget( + CommonElevationBehavior, + RectangularRippleBehavior, + ButtonBehavior, + MDWidget, + ): + _elev = 0 # previous elevation value + + def on_press(self, *args): + if not self._elev: + self._elev = self.elevation + Animation(elevation=self.elevation + 2, d=0.4).start(self) + + def on_release(self, *args): + Animation.cancel_all(self, "elevation") + Animation(elevation=self._elev, d=0.1).start(self) + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.animation import Animation + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior + from kivymd.uix.screen import MDScreen + from kivymd.uix.widget import MDWidget + + + class ElevatedWidget( + CommonElevationBehavior, + RectangularRippleBehavior, + ButtonBehavior, + MDWidget, + ): + _elev = 0 # previous elevation value + + def on_press(self, *args): + if not self._elev: + self._elev = self.elevation + Animation(elevation=self.elevation + 2, d=0.4).start(self) + + def on_release(self, *args): + Animation.cancel_all(self, "elevation") + Animation(elevation=self._elev, d=0.1).start(self) + + + class Example(MDApp): + def build(self): + return ( + MDScreen( + ElevatedWidget( + pos_hint={'center_x': .5, 'center_y': .5}, + size_hint=(None, None), + size=(100, 100), + md_bg_color="blue", + elevation=4, + radius=18, + ) + ) + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif + :align: center +""" + +from __future__ import annotations + +__all__ = ( + "CommonElevationBehavior", + "RectangularElevationBehavior", + "CircularElevationBehavior", + "RoundedRectangularElevationBehavior", + "FakeRectangularElevationBehavior", + "FakeCircularElevationBehavior", +) + +import os + +from kivy import Logger +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.graphics import RenderContext, RoundedRectangle +from kivy.properties import ( + AliasProperty, + BooleanProperty, + BoundedNumericProperty, + ColorProperty, + ListProperty, + NumericProperty, + ObjectProperty, + VariableListProperty, +) +from kivy.uix.widget import Widget + +from kivymd import glsl_path + + +# FIXME: Add shadow manipulation with canvas instructions such as +# PushMatrix and PopMatrix. +class CommonElevationBehavior(Widget): + """Common base class for rectangular and circular elevation behavior.""" + + elevation = BoundedNumericProperty(0, min=0, errorvalue=0) + """ + Elevation of the widget. + :attr:`elevation` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `0`. """ - # Shadow rendering properties. - # Shadow rotation memory - SHARED ACROSS OTHER CLASSES. - angle = NumericProperty(0) - """ - Angle of rotation in degrees of the current shadow. - This value is shared across different widgets. - - .. note:: - This value will affect both, hard and soft shadows. - Each shadow has his own origin point that's computed every time the - elevation changes. - - .. warning:: - Do not add `PushMatrix` inside the canvas before and add `PopMatrix` - in the next layer, this will cause visual errors, because the stack - used will clip the push and pop matrix already inside the canvas.before - canvas layer. - - Incorrect: - - .. code-block:: kv - - - canvas.before: - PushMatrix - [...] - canvas: - PopMatrix - - Correct: - - .. code-block:: kv - - - canvas.before: - PushMatrix - [...] - PopMatrix - - - - :attr:`angle` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - radius = VariableListProperty([0]) + shadow_radius = VariableListProperty([0], length=4) """ Radius of the corners of the shadow. - This values represents each corner of the shadow, starting from `top-left` - corner and going clockwise. + + .. versionadded:: 1.1.0 + + You don't have to use this parameter. + The radius of the elevation effect is calculated automatically one way + or another based on the radius of the parent widget, for example: .. code-block:: python - radius = [ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - ] + from kivy.lang import Builder - This value can be expanded thus allowing this settings to be valid: + from kivymd.app import MDApp - .. code-block:: python + KV = ''' + MDScreen: - widget.radius=[0] # Translates to [0, 0, 0, 0] - widget.radius=[10, 3] # Translates to [10, 3, 10, 3] - widget.radius=[7.0, 8.7, 1.5, 3.0] # Translates to [7, 8, 1, 3] + MDCard: + radius: 12, 46, 12, 46 + size_hint: .5, .3 + pos_hint: {"center_x": .5, "center_y": .5} + elevation: 4 + shadow_softness: 8 + shadow_offset: (-2, 2) + ''' + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + + Test().run() + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-radius.png + :align: center .. note:: - This value will affect both, hard and soft shadows. - This value only affects :class:`~RoundedRectangularElevationBehavior` - for now, but can be stored and used by custom shadow draw functions. + However, if you want to use this parameter, remember that the angle + values for the radius of the Kivy widgets and the radius for the shader + are different. - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + .. code-block:: python + + shadow_radius = ['top-right', 'bot-right', 'top-left', 'bot-left'] + kivy_radius = ['top-left', 'top-right', 'bottom-right', 'bottom-left'] + + :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty` and defaults to `[0, 0, 0, 0]`. """ - # Position of the shadow. - _shadow_origin_x = NumericProperty(0) + shadow_softness = NumericProperty(12) """ - Shadow origin `x` position for the rotation origin. + Softness of the shadow. - Managed by `_shadow_origin`. - - :attr:`_shadow_origin_x` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - - .. note:: - This property is automatically processed. by _shadow_origin. - """ - - _shadow_origin_y = NumericProperty(0) - """ - Shadow origin y position for the rotation origin. - - Managed by :attr:`_shadow_origin`. - - :attr:`_shadow_origin_y` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - - .. note:: - This property is automatically processed. - """ - - _shadow_origin = ReferenceListProperty(_shadow_origin_x, _shadow_origin_y) - """ - Soft shadow rotation origin point. - - :attr:`_shadow_origin` is an :class:`~kivy.properties.ReferenceListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed and relative to the canvas center. - """ - - _shadow_pos = ListProperty([0, 0]) # custom offset - """ - Soft shadow origin point. - - :attr:`_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed and relative to the widget's - canvas center. - """ - - shadow_pos = ListProperty([0, 0]) # bottom left corner - """ - Custom shadow origin point. If this property is set, :attr:`_shadow_pos` - will be ommited. - - This property allows users to fake light source. - - :attr:`shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - this value overwrite the :attr:`_shadow_pos` processing. - """ - - # Shadow Group shared memory - __shadow_groups = {"global": []} - - shadow_group = StringProperty("global") - """ - Widget's shadow group. - By default every widget with a shadow is saved inside the memory - :attr:`__shadow_groups` as a weakref. This means that you can have multiple - light sources, one for every shadow group. - - To fake a light source use :attr:`force_shadow_pos`. - - :attr:`shadow_group` is an :class:`~kivy.properties.StringProperty` - and defaults to `"global"`. - """ - - _elevation = BoundedNumericProperty(0, min=0, errorvalue=0) - """ - Current elevation of the widget. - - .. warning:: - This property is the current elevation of the widget, do not - use this property directly, instead, use :class:`~CommonElevationBehavior` - elevation. - - :attr:`_elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - # soft shadow - _soft_shadow_texture = ObjectProperty() - """ - Texture of the soft shadow texture for the canvas. - - :attr:`_soft_shadow_texture` is an :class:`~kivy.core.image.Image` - and defaults to `None`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_size = ListProperty([0, 0]) - """ - Size of the soft shadow texture over the canvas. - - :attr:`soft_shadow_size` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_pos = ListProperty([0, 0]) - """ - Position of the hard shadow texture over the canvas. - - :attr:`soft_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_cl = ListProperty([0, 0, 0, 0.50]) - """ - Color of the soft shadow. - - :attr:`soft_shadow_cl` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0.15]`. - """ - - # hard shadow - hard_shadow_texture = ObjectProperty() - """ - Texture of the hard shadow texture for the canvas. - - :attr:`hard_shadow_texture` is an :class:`~kivy.core.image.Image` - and defaults to `None`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_size = ListProperty([0, 0]) - """ - Size of the hard shadow texture over the canvas. - - :attr:`hard_shadow_size` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_pos = ListProperty([0, 0]) - """ - Position of the hard shadow texture over the canvas. - - :attr:`hard_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_cl = ListProperty([0, 0, 0, 0.15]) - """ - Color of the hard shadow. - - .. note:: - :attr:`hard_shadow_cl` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0.15]`. - """ - - # Shared property for some calculations. - # This values are used to improve the gaussain blur and avoid that - # the blur goes outside the texture. - hard_shadow_offset = BoundedNumericProperty( - 2, min=0, errorhandler=lambda x: 0 if x < 0 else x - ) - """ - This value sets a special offset to the shadow canvas, this offset allows a - correct draw of the canvas size. allowing the effect to correctly blur the - image in the given space. - - :attr:`hard_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `2`. - """ - - soft_shadow_offset = BoundedNumericProperty( - 4, min=0, errorhandler=lambda x: 0 if x < 0 else x - ) - """ - This value sets a special offset to the shadow canvas, this offset allows a - correct draw of the canvas size. allowing the effect to correctly blur the - image in the given space. - - :attr:`soft_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `4`. - """ - - draw_shadow = ObjectProperty(None) - """ - This property controls the draw call of the context. - - This property is automatically set to :attr:`__draw_shadow__` inside the - `super().__init__ call.` unless the property is different of None. - - To set a different drawing instruction function, set this property before the - `super(),__init__` call inside the `__init__` definition of the new class. - - You can use the source for this classes as example of how to draw over - with the context: - - Real time shadows: - #. :class:`~RectangularElevationBehavior` - #. :class:`~CircularElevationBehavior` - #. :class:`~RoundedRectangularElevationBehavior` - #. :class:`~ObservableShadow` - - - Fake shadows (d`ont use this property): - #. :class:`~FakeRectangularElevationBehavior` - #. :class:`~FakeCircularElevationBehavior` - - :attr:`draw_shadow` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - - .. note:: If this property is left to `None` the - :class:`~CommonElevationBehavior` will set to a function that will - raise a `NotImplementedError` inside `super().__init__`. - - Follow the next example to set a new draw instruction for the class - inside `__init__`: + .. versionadded:: 1.1.0 .. code-block:: python - class RoundedRectangularElevationBehavior(CommonElevationBehavior): - ''' - Shadow class for the RoundedRectangular shadow behavior. - Controls the size and position of the shadow. - ''' + from kivy.lang import Builder - def __init__(self, **kwargs): - self._draw_shadow = WeakMethod(self.__draw_shadow__) - super().__init__(**kwargs) + from kivymd.app import MDApp + from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior - def __draw_shadow__(self, origin, end, context=None): - context.draw(...) + KV = ''' + + size_hint: None, None + size: "250dp", "50dp" - Context is a `Pillow` `ImageDraw` class. For more information check the - [Pillow official documentation](https://github.com/python-pillow/Pillow/). + + MDScreen: + + RectangularElevationButton: + pos_hint: {"center_x": .5, "center_y": .6} + elevation: 6 + shadow_softness: 6 + + RectangularElevationButton: + pos_hint: {"center_x": .5, "center_y": .4} + elevation: 6 + shadow_softness: 12 + ''' + + + class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior): + md_bg_color = [0, 0, 1, 1] + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-softness.png + :align: center + + :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` + and defaults to `12`. """ - # All classes that uses a fake shadow shall set this value as `True` - # for performance. - _fake_elevation = BooleanProperty(False) + shadow_offset = ListProperty((0, 2)) + """ + Offset of the shadow. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior + + KV = ''' + + size_hint: None, None + size: "100dp", "100dp" + + + MDScreen: + + RectangularElevationButton: + pos_hint: {"center_x": .5, "center_y": .5} + elevation: 6 + shadow_radius: 18 + shadow_softness: 24 + shadow_offset: 12, 12 + ''' + + + class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior): + md_bg_color = [0, 0, 1, 1] + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-1.png + :align: center + + .. code-block:: kv + + RectangularElevationButton: + shadow_offset: -12, 12 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png + :align: center + + .. code-block:: kv + + RectangularElevationButton: + shadow_offset: -12, -12 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png + :align: center + + .. code-block:: kv + + RectangularElevationButton: + shadow_offset: 12, -12 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png + :align: center + + :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` + and defaults to `(0, 2)`. + """ + + shadow_color = ColorProperty([0, 0, 0, 0.6]) + """ + Offset of the shadow. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + RectangularElevationButton: + shadow_color: 0, 0, 1, .8 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png + :align: center + + :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `[0.4, 0.4, 0.4, 0.8]`. + """ + + _transition_ref = ObjectProperty() + _has_relative_position = BooleanProperty(defaultvalue=False) + _elevation = 0 + _shadow_color = [0.0, 0.0, 0.0, 0.0] + + def _get_window_pos(self, *args): + window_pos = self.to_window(*self.pos) + # To list, so it can be compared to self.pos directly. + return [window_pos[0], window_pos[1]] + + def _set_window_pos(self, value): + self.window_pos = value + + window_pos = AliasProperty(_get_window_pos, _set_window_pos) def __init__(self, **kwargs): - if self.draw_shadow is None: - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self.prev_shadow_group = None - im = BytesIO() - Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") - im.seek(0) - # Setting a empty image as texture, improves performance. - self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( - im, ext="png" - ).texture - Clock.schedule_once(self.shadow_preset, -1) - self.on_shadow_group(self, self.shadow_group) - - self.bind( - pos=self._update_shadow, - size=self._update_shadow, - radius=self._update_shadow, - ) super().__init__(**kwargs) - def on_shadow_group(self, instance, value): + with self.canvas.before: + self.context = RenderContext(use_parent_projection=True) + with self.context: + self.rect = RoundedRectangle(pos=self.pos, size=self.size) + + Clock.schedule_once(self.after_init) + + def after_init(self, *args): + Clock.schedule_once(self.check_for_relative_behavior) + Clock.schedule_once(self.set_shader_string) + Clock.schedule_once(lambda x: self.on_elevation(self, self.elevation)) + self.on_pos() + + def check_for_relative_behavior(self, *args) -> None: """ - This function controls the shadow group of the widget. - Do not use Directly to change the group. instead, use the shadow_group - :attr:`property`. + Checks if the widget has relative properties and if necessary + binds Window.on_draw and screen events to fix behavior """ - groups = CommonElevationBehavior.__shadow_groups - if self.prev_shadow_group: - group = groups[self.prev_shadow_group] - for widget in group[:]: - if widget() is self: - group.remove(widget) - group = self.prev_shadow_group = self.shadow_group - if group not in groups: - groups[group] = [] - r = ref(self, CommonElevationBehavior._clear_shadow_groups) - groups[group].append(r) + if self.pos != self.window_pos: + self._has_relative_position = True - @staticmethod - def _clear_shadow_groups(wk): - # auto flush the element when the weak reference have been deleted - groups = CommonElevationBehavior.__shadow_groups - for group in list(groups.values()): - if not group: - break - if wk in group: - group.remove(wk) + # Loops to check if its inside screenmanager or bottom_navigation. + widget = self + while True: + # Checks if has screen event function + # works for Screen and MDTab objects. + if hasattr(widget, "on_pre_enter"): + widget.bind(on_pre_enter=self.apply_correction) + widget.bind(on_pre_leave=self.apply_correction) + widget.bind(on_enter=self.reset_correction) + widget.bind(on_leave=self.reset_correction) + self._has_relative_position = True + + # Save refs to objects with transition property. + if hasattr(widget, "header"): # specific to bottom_nav + self._transition_ref = widget.header.panel + elif hasattr(widget, "manager"): # specific to screen + if widget.manager: # manager cant be None + self._transition_ref = widget.manager break - def force_shadow_pos(self, shadow_pos): - """ - This property forces the shadow position in every widget inside the - widget. The argument :attr:`shadow_pos` is expected as a - or . - """ - - if self.shadow_group is None: - return - group = CommonElevationBehavior.__shadow_groups[self.shadow_group] - for wk in group[:]: - widget = wk() - if widget is None: - group.remove(wk) - widget.shadow_pos = shadow_pos - del group - - def update_group_property(self, property_name, value): - """ - This functions allows to change properties of every widget inside the - shadow group. - """ - - if self.shadow_group is None: - return - group = CommonElevationBehavior.__shadow_groups[self.shadow_group] - for wk in group[:]: - widget = wk() - if widget is None: - group.remove(wk) - setattr(widget, property_name, value) - del group - - def shadow_preset(self, *args): - """ - This function is meant to set the default configuration of the - elevation. - - After a new instance is created, the elevation property will be launched - and thus this function will update the elevation if the KV lang have not - done it already. - - Works similar to an `__after_init__` call inside a widget. - """ - - from kivymd.uix.card import MDCard - - if self.elevation is None and not issubclass(self.__class__, MDCard): - self.elevation = 10 - if self._fake_elevation is False: - self._update_shadow(self, self.elevation) - self.bind( - pos=self._update_shadow, - size=self._update_shadow, - _elevation=self._update_shadow, - ) - - def on_elevation(self, instance, value): - """ - Elevation event that sets the current elevation value to `_elevation`. - """ - - if value is not None: - self._elevation = value - - def _set_soft_shadow_a(self, value): - value = 0 if value < 0 else (1 if value > 1 else value) - self.soft_shadow_cl[-1] = value - return True - - def _set_hard_shadow_a(self, value): - value = 0 if value < 0 else (1 if value > 1 else value) - self.hard_shadow_cl[-1] = value - return True - - def _get_soft_shadow_a(self): - return self.soft_shadow_cl[-1] - - def _get_hard_shadow_a(self): - return self.hard_shadow_cl[-1] - - _soft_shadow_a = AliasProperty( - _get_soft_shadow_a, _set_soft_shadow_a, bind=["soft_shadow_cl"] - ) - _hard_shadow_a = AliasProperty( - _get_hard_shadow_a, _set_hard_shadow_a, bind=["hard_shadow_cl"] - ) - - def on_disabled(self, instance, value): - """ - This function hides the shadow when the widget is disabled. - It sets the shadow to `0`. - """ - - if self.disabled is True: - self._elevation = 0 - else: - self._elevation = 0 if self.elevation is None else self.elevation - self._update_shadow(self, self._elevation) - try: - super().on_disabled(instance, value) - except Exception: - pass - - def _update_elevation(self, instance, value): - self._elevation = value - self._update_shadow(instance, value) - - def _update_shadow_pos(self, instance, value): - if self._elevation > 0: - self.hard_shadow_pos = [ - self.x - dp(self.hard_shadow_offset), # + self.shadow_pos[0], - self.y - dp(self.hard_shadow_offset), # + self.shadow_pos[1], - ] - if self.shadow_pos == [0, 0]: - self.soft_shadow_pos = [ - self.x - + self._shadow_pos[0] - - self._elevation - - dp(self.soft_shadow_offset), - self.y - + self._shadow_pos[1] - - self._elevation - - dp(self.soft_shadow_offset), - ] + elif widget.parent and str(widget) != str(widget.parent): + widget = widget.parent else: - self.soft_shadow_pos = [ - self.x - + self.shadow_pos[0] - - self._elevation - - dp(self.soft_shadow_offset), - self.y - + self.shadow_pos[1] - - self._elevation - - dp(self.soft_shadow_offset), - ] - self._shadow_origin = [ - self.soft_shadow_pos[0] + self.soft_shadow_size[0] / 2, - self.soft_shadow_pos[1] + self.soft_shadow_size[1] / 2, + break + + if self._has_relative_position: + Window.bind(on_draw=self.update_window_position) + + def apply_correction(self, *args): + if self._transition_ref: + transition = str(self._transition_ref.transition) + # Slide and Card transitions only need _has_relative_pos to be + # always on. + if ( + "SlideTransition" in transition + or "CardTransition" in transition + ): + self.context.use_parent_modelview = False + else: + self.context.use_parent_modelview = True + + def reset_correction(self, *args): + self.context.use_parent_modelview = False + self.update_window_position() + + def get_shader_string(self) -> str: + shader_string = "" + for name_file in ["header.frag", "elevation.frag", "main.frag"]: + with open( + os.path.join(glsl_path, "elevation", name_file), + encoding="utf-8", + ) as file: + shader_string += f"{file.read()}\n\n" + + return shader_string + + def set_shader_string(self, *args) -> None: + self.context["shadow_radius"] = list(map(float, self.shadow_radius)) + self.context["shadow_softness"] = float(self.shadow_softness) + self.context["shadow_color"] = list(map(float, self.shadow_color))[ + :-1 + ] + [float(self.opacity)] + self.context["pos"] = list(map(float, self.rect.pos)) + self.context.shader.fs = self.get_shader_string() + + def update_resolution(self) -> None: + self.context["resolution"] = (*self.rect.size, *self.rect.pos) + + def on_shadow_color(self, instance, value) -> None: + def on_shadow_color(*args): + self._shadow_color = list(map(float, value))[:-1] + [ + float(self.opacity) if not self.disabled else 0 ] + self.context["shadow_color"] = self._shadow_color - def on__shadow_pos(self, ins, val): - """ - Updates the shadow with the computed value. + Clock.schedule_once(on_shadow_color) - Call this function every time you need to force a shadow update. - """ + def on_shadow_radius(self, instance, value) -> None: + def on_shadow_radius(*args): + if hasattr(self, "context"): + self.context["shadow_radius"] = list(map(float, value)) - self._update_shadow_pos(ins, val) + Clock.schedule_once(on_shadow_radius) - def on_shadow_pos(self, ins, val): - """ - Updates the shadow with the fixed value. + def on_shadow_softness(self, instance, value) -> None: + def on_shadow_softness(*args): + if hasattr(self, "context"): + self.context["shadow_softness"] = float(value) - Call this function every time you need to force a shadow update. - """ + Clock.schedule_once(on_shadow_softness) - self._update_shadow_pos(ins, val) - - def _update_shadow(self, instance, value): - self._update_shadow_pos(instance, value) - if self._elevation > 0 and self._fake_elevation is False: - # dynamic elevation position for the shadow - if self.shadow_pos == [0, 0]: - self._shadow_pos = [0, -self._elevation * 0.4] - - # HARD Shadow - offset = int(dp(self.hard_shadow_offset)) - size = [ - int(self.size[0] + (offset * 2)), - int(self.size[1] + (offset * 2)), - ] - im = BytesIO() - # context - img = Image.new("RGBA", tuple(size), color=(0, 0, 0, 0)) - # draw context - shadow = ImageDraw.Draw(img) - self.draw_shadow()( - [offset, offset], - [ - int(size[0] - 1 - offset), - int(size[1] - 1 - offset), - ], - context=shadow - # context=ref(shadow) - ) - img = img.filter( - ImageFilter.GaussianBlur( - radius=int(dp(1 + self.hard_shadow_offset / 3)) + def on_elevation(self, instance, value) -> None: + def on_elevation(*args): + if hasattr(self, "context"): + self._elevation = value + self.hide_elevation( + True if (value <= 0 or self.disabled) else False ) - ) - img.save(im, format="png") - im.seek(0) - self.hard_shadow_size = size - self.hard_shadow_texture = CoreImage(im, ext="png").texture - # soft shadow - if self.soft_shadow_cl[-1] > 0: - offset = dp(self.soft_shadow_offset) - size = [ - int(self.size[0] + dp(self._elevation * 2) + (offset * 2)), - int(self.size[1] + dp(self._elevation * 2) + (offset * 2)), - # ((self._elevation)*2) + x + (offset*2)) for x in self.size - ] - im = BytesIO() - img = Image.new("RGBA", tuple(size), color=((0,) * 4)) - shadow = ImageDraw.Draw(img) - _offset = int(dp(self._elevation + offset)) - self.draw_shadow()( - [ - _offset, - _offset, - ], - [int(size[0] - _offset - 1), int(size[1] - _offset - 1)], - context=shadow - # context=ref(shadow) - ) - img = img.filter( - ImageFilter.GaussianBlur(radius=self._elevation // 2) - ) - shadow = ImageDraw.Draw(img) - img.save(im, format="png") - im.seek(0) - self.soft_shadow_size = size - self._soft_shadow_texture = CoreImage(im, ext="png").texture - else: - im = BytesIO() - Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") - im.seek(0) - self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( - im, ext="png" - ).texture + Clock.schedule_once(on_elevation) + + def on_shadow_offset(self, instance, value) -> None: + self.on_size() + self.on_pos() + + def update_window_position(self, *args) -> None: + """ + This function is used only when the widget has relative position + properties. + """ + + self.on_pos() + + def on_pos(self, *args) -> None: + if not hasattr(self, "rect"): return - def _get_center(self): - center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] - return center + if ( + self._has_relative_position + and not self.context.use_parent_modelview + ): + pos = self.window_pos + else: + pos = self.pos - def __draw_shadow__(self, origin, end, context=None): - Logger.warning( - f"KivyMD: " - f"If you see this error, this means that either youre using " - f"`CommonElevationBehavio`r directly or your 'shader' dont have a " - f"`_draw_shadow` instruction, remember to overwrite this function" - f"to draw over the image context. Тhe figure you would like. " - f"Or your class {self.__class__.__name__} is not inherited from " - f"any of the classes {__all__}" + self.rect.pos = [ + pos[0] + - ((self.rect.size[0] - self.width) / 2) + - self.shadow_offset[0], + pos[1] + - ((self.rect.size[1] - self.height) / 2) + - self.shadow_offset[1], + ] + + self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0] + self.context["pos"] = list(map(float, self.rect.pos)) + self.update_resolution() + + def on_size(self, *args) -> None: + if not hasattr(self, "rect"): + return + + # If the elevation value is 0, set the canvas size to zero. + # Because even with a zero elevation value, the shadow is displayed + # under the widget. This is visible if we change the scale + # of the widget. + width = self.size[0] if self.elevation else 0 + height = self.size[1] if self.elevation else 0 + self.rect.size = ( + width + (self._elevation * self.shadow_softness / 2), + height + (self._elevation * self.shadow_softness / 2), ) + self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0] + self.context["size"] = list(map(float, self.rect.size)) + self.update_resolution() + + def on_opacity(self, instance, value: int | float) -> None: + """ + Adjusts the transparency of the shadow according to the transparency + of the widget. + """ + + def on_opacity(*args): + self._shadow_color = list(map(float, self._shadow_color))[:-1] + [ + float(value) + ] + self.context["shadow_color"] = self._shadow_color + + super().on_opacity(instance, value) + Clock.schedule_once(on_opacity) + + def on_radius(self, instance, value) -> None: + self.shadow_radius = [value[1], value[2], value[0], value[3]] + + def on_disabled(self, instance, value) -> None: + if value: + self._elevation = 0 + self.hide_elevation(True) + else: + self.hide_elevation(False) + + def hide_elevation(self, hide: bool) -> None: + if hide: + self._elevation = -self.elevation + self._shadow_color = [0.0, 0.0, 0.0, 0.0] + else: + self._elevation = self.elevation + self._shadow_color = self.shadow_color[:-1] + [float(self.opacity)] + + self.on_shadow_color(self, self._shadow_color) + self.on_size() + self.on_pos() + class RectangularElevationBehavior(CommonElevationBehavior): """ - Base class for a rectangular elevation behavior. + .. deprecated:: 1.1.0 + Use :class:`~CommonElevationBehavior` class instead. """ def __init__(self, **kwargs): - self.draw_shadow = WeakMethod(self.__draw_shadow__) super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - context.rectangle(origin + end, fill=tuple([255] * 4)) + Logger.warning( + "KivyMD: " + "The `RectangularElevationBehavior` class has been deprecated. " + "Use the `CommonElevationBehavior` class instead.`" + ) class CircularElevationBehavior(CommonElevationBehavior): """ - Base class for a circular elevation behavior. + .. deprecated:: 1.1.0 + Use :class:`~CommonElevationBehavior` class instead. """ def __init__(self, **kwargs): - self.draw_shadow = WeakMethod(self.__draw_shadow__) super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - context.ellipse(origin + end, fill=tuple([255] * 4)) + Logger.warning( + "KivyMD: " + "The `CircularElevationBehavior` class has been deprecated. " + "Use the `CommonElevationBehavior` class instead.`" + ) class RoundedRectangularElevationBehavior(CommonElevationBehavior): """ - Base class for rounded rectangular elevation behavior. + .. deprecated:: 1.1.0 + Use :class:`~CommonElevationBehavior` class instead. """ def __init__(self, **kwargs): - self.bind( - radius=self._update_shadow, - ) - self.draw_shadow = WeakMethod(self.__draw_shadow__) super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - if self.radius == [0, 0, 0, 0]: - context.rectangle(origin + end, fill=tuple([255] * 4)) - else: - radius = [x * 2 for x in self.radius] - context.pieslice( - [ - origin[0], - origin[1], - origin[0] + radius[0], - origin[1] + radius[0], - ], - 180, - 270, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - end[0] - radius[1], - origin[1], - end[0], - origin[1] + radius[1], - ], - 270, - 360, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - end[0] - radius[2], - end[1] - radius[2], - end[0], - end[1], - ], - 0, - 90, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - origin[0], - end[1] - radius[3], - origin[0] + radius[3], - end[1], - ], - 90, - 180, - fill=(255, 255, 255, 255), - ) - if all((x == self.radius[0] for x in self.radius)): - radius = int(self.radius[0]) - context.rectangle( - [ - origin[0] + radius, - origin[1], - end[0] - radius, - end[1], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0], - origin[1] + radius, - end[0], - end[1] - radius, - ], - fill=(255,) * 4, - ) - else: - radius = [ - max((self.radius[0], self.radius[1])), - max((self.radius[1], self.radius[2])), - max((self.radius[2], self.radius[3])), - max((self.radius[3], self.radius[0])), - ] - context.rectangle( - [ - origin[0] + self.radius[0], - origin[1], - end[0] - self.radius[1], - end[1] - radius[2], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0] + radius[3], - origin[1] + self.radius[1], - end[0], - end[1] - self.radius[2], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0] + self.radius[3], - origin[1] + radius[0], - end[0] - self.radius[2], - end[1], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0], - origin[1] + self.radius[0], - end[0] - radius[2], - end[1] - self.radius[3], - ], - fill=(255,) * 4, - ) - - -class ObservableShadow(CommonElevationBehavior): - """ - ObservableShadow is real time shadow render that it's intended to only - render a partial shadow of widgets based upon on the window observable - area, this is meant to improve the performance of bigger widgets. - - .. warning:: - This is an empty class, the name has been reserved for future use. - if you include this clas in your object, you wil get a - `NotImplementedError`. - """ - - def __init__(self, **kwargs): - # self._shadow = MDApp.get_running_app().theme_cls.round_shadow - # self._fake_elevation=True - raise NotImplementedError( - "ObservableShadow:\n\t" "This class is in current development" + Logger.warning( + "KivyMD: " + "The `RoundedRectangularElevationBehavior` class has been " + "deprecated. Use the `CommonElevationBehavior` class instead.`" ) - super().__init__(**kwargs) class FakeRectangularElevationBehavior(CommonElevationBehavior): """ - `FakeRectangularElevationBehavio`r is a shadow mockup for widgets. Improves - performance using cached images inside `kivymd.images` dir - - This class cast a fake Rectangular shadow behaind the widget. - - You can either use this behavior to overwrite the elevation of a prefab - widget, or use it directly inside a new widget class definition. - - Use this class as follows for new widgets: - - .. code-block:: python - - class NewWidget( - ThemableBehavior, - FakeCircularElevationBehavior, - SpecificBackgroundColorBehavior, - # here you add the other front end classes for the widget front_end, - ): - [...] - - With this method each class can draw it's content in the canvas in the - correct order, avoiding some visual errors. - - `FakeCircularElevationBehavior` will load prefabricated textures to - optimize loading times. - - .. note:: About rounded corners: - be careful, since this behavior is a mockup and will not draw any - rounded corners. + .. deprecated:: 1.1.0 + Use :class:`~CommonElevationBehavior` class instead. """ def __init__(self, **kwargs): - # self._shadow = MDApp.get_running_app().theme_cls.round_shadow - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self._fake_elevation = True - self._update_shadow(self, self.elevation) super().__init__(**kwargs) - - def _update_shadow(self, *args): - if self._elevation > 0: - # Set shadow size. - ratio = self.width / (self.height if self.height != 0 else 1) - if -2 < ratio < 2: - self._shadow = MDApp.get_running_app().theme_cls.quad_shadow - width = soft_width = self.width * 1.9 - height = soft_height = self.height * 1.9 - elif ratio <= -2: - self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow - ratio = abs(ratio) - if ratio > 5: - ratio = ratio * 22 - else: - ratio = ratio * 11.5 - width = soft_width = self.width * 1.9 - height = self.height + dp(ratio) - soft_height = ( - self.height + dp(ratio) + dp(self._elevation) * 0.5 - ) - else: - self._shadow = MDApp.get_running_app().theme_cls.quad_shadow - width = soft_width = self.width * 1.8 - height = soft_height = self.height * 1.8 - - self.soft_shadow_size = (soft_width, soft_height) - self.hard_shadow_size = (width, height) - # Set ``soft_shadow`` parameters. - center_x, center_y = self._get_center() - self.hard_shadow_pos = self.soft_shadow_pos = ( - center_x - soft_width / 2, - center_y - soft_height / 2 - dp(self._elevation * 0.5), - ) - # Set transparency - self._soft_shadow_a = 0.1 * 1.05**self._elevation - self._hard_shadow_a = 0.4 * 0.8**self._elevation - t = int(round(self._elevation)) - if 0 < t <= 23: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures[str(t)] - else: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures["23"] - else: - self._soft_shadow_a = 0 - self._hard_shadow_a = 0 - - def __draw_shadow__(self, origin, end, context=None): - pass + Logger.warning( + "KivyMD: " + "The `FakeRectangularElevationBehavior` class has been " + "deprecated. Use the `CommonElevationBehavior` class instead." + ) class FakeCircularElevationBehavior(CommonElevationBehavior): """ - `FakeCircularElevationBehavior` is a shadow mockup for widgets. Improves - performance using cached images inside `kivymd.images` dir - - This class cast a fake elliptic shadow behaind the widget. - - You can either use this behavior to overwrite the elevation of a prefab - widget, or use it directly inside a new widget class definition. - - Use this class as follows for new widgets: - - .. code-block:: python - - class NewWidget( - ThemableBehavior, - FakeCircularElevationBehavior, - SpecificBackgroundColorBehavior, - # here you add the other front end classes for the widget front_end, - ): - [...] - - With this method each class can draw it's content in the canvas in the - correct order, avoiding some visual errors. - - `FakeCircularElevationBehavior` will load prefabricated textures to optimize - loading times. - - .. note:: About rounded corners: - be careful, since this behavior is a mockup and will not draw any rounded - corners. only perfect ellipses. + .. deprecated:: 1.1.0 + Use :class:`~CommonElevationBehavior` class instead. """ def __init__(self, **kwargs): - self._shadow = MDApp.get_running_app().theme_cls.round_shadow - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self._fake_elevation = True - self._update_shadow(self, self.elevation) super().__init__(**kwargs) - - def _update_shadow(self, *args): - if self._elevation > 0: - # set shadow size - width = self.width * 2 - height = self.height * 2 - center_x, center_y = self._get_center() - - x = center_x - width / 2 - self.soft_shadow_size = (width, height) - self.hard_shadow_size = (width, height) - # set ``soft_shadow`` parameters - y = center_y - height / 2 - dp(0.5 * self._elevation) - self.soft_shadow_pos = (x, y) - - # set ``hard_shadow`` parameters - y = center_y - height / 2 - dp(0.5 * self._elevation) - self.hard_shadow_pos = (x, y) - - # shadow transparency - self._soft_shadow_a = 0.1 * 1.05**self._elevation - self._hard_shadow_a = 0.4 * 0.8**self._elevation - t = int(round(self._elevation)) - if 0 < t <= 23: - if hasattr(self, "_shadow"): - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures[str(t)] - else: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures["23"] - else: - self._soft_shadow_a = 0 - self._hard_shadow_a = 0 - - def __draw_shadow__(self, origin, end, context=None): - pass + Logger.warning( + "KivyMD: " + "The `FakeCircularElevationBehavior` class has been deprecated. " + "Use the `CommonElevationBehavior` class instead." + ) diff --git a/sbapp/kivymd/uix/behaviors/focus_behavior.py b/sbapp/kivymd/uix/behaviors/focus_behavior.py index 15d3169..b90f16a 100644 --- a/sbapp/kivymd/uix/behaviors/focus_behavior.py +++ b/sbapp/kivymd/uix/behaviors/focus_behavior.py @@ -100,23 +100,35 @@ class FocusBehavior(HoverBehavior, ButtonBehavior): def on_enter(self): """Called when mouse enter the bbox of the widget.""" - if hasattr(self, "md_bg_color") and self.focus_behavior: + if ( + hasattr(self, "md_bg_color") or hasattr(self, "bg_color") + ) and self.focus_behavior: if hasattr(self, "theme_cls") and not self.focus_color: - self.md_bg_color = self.theme_cls.bg_normal + color = self.theme_cls.bg_normal else: if not self.focus_color: - self.md_bg_color = App.get_running_app().theme_cls.bg_normal + color = App.get_running_app().theme_cls.bg_normal else: - self.md_bg_color = self.focus_color + color = self.focus_color + self._set_bg_color(color) def on_leave(self): """Called when the mouse exit the widget.""" - if hasattr(self, "md_bg_color") and self.focus_behavior: + if ( + hasattr(self, "md_bg_color") or hasattr(self, "bg_color") + ) and self.focus_behavior: if hasattr(self, "theme_cls") and not self.unfocus_color: - self.md_bg_color = self.theme_cls.bg_light + color = self.theme_cls.bg_light else: if not self.unfocus_color: - self.md_bg_color = App.get_running_app().theme_cls.bg_light + color = App.get_running_app().theme_cls.bg_light else: - self.md_bg_color = self.unfocus_color + color = self.unfocus_color + self._set_bg_color(color) + + def _set_bg_color(self, color): + if hasattr(self, "md_bg_color"): + self.md_bg_color = color + elif hasattr(self, "bg_color"): + self.bg_color = color diff --git a/sbapp/kivymd/uix/behaviors/ripple_behavior.py b/sbapp/kivymd/uix/behaviors/ripple_behavior.py index f06e724..1271878 100755 --- a/sbapp/kivymd/uix/behaviors/ripple_behavior.py +++ b/sbapp/kivymd/uix/behaviors/ripple_behavior.py @@ -111,7 +111,7 @@ from kivy.properties import ( from kivy.uix.behaviors import ToggleButtonBehavior -class CommonRipple(object): +class CommonRipple: """Base class for ripple effect.""" ripple_rad_default = NumericProperty(1) diff --git a/sbapp/kivymd/uix/behaviors/rotate_behavior.py b/sbapp/kivymd/uix/behaviors/rotate_behavior.py new file mode 100644 index 0000000..c80b879 --- /dev/null +++ b/sbapp/kivymd/uix/behaviors/rotate_behavior.py @@ -0,0 +1,133 @@ +""" +Behaviors/Rotate +================ + +.. versionadded:: 1.1.0 + +Base class for controlling the rotate of the widget. + +.. note:: See `kivy.graphics.Rotate + `_ + for more information. + +Kivy +---- + +.. code-block:: python + + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.app import App + from kivy.properties import NumericProperty + from kivy.uix.button import Button + + KV = ''' + Screen: + + RotateButton: + size_hint: .5, .5 + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.change_rotate(self) + + canvas.before: + PushMatrix + Rotate: + angle: self.rotate_value_angle + axis: 0, 0, 1 + origin: self.center + canvas.after: + PopMatrix + ''' + + + class RotateButton(Button): + rotate_value_angle = NumericProperty(0) + + + class Test(App): + def build(self): + return Builder.load_string(KV) + + def change_rotate(self, instance_button: Button) -> None: + Animation(rotate_value_angle=45, d=0.3).start(instance_button) + + + Test().run() + +KivyMD +------ + +.. code-block:: python + + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import RotateBehavior + from kivymd.uix.boxlayout import MDBoxLayout + + KV = ''' + MDScreen: + + RotateBox: + size_hint: .5, .5 + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.change_rotate(self) + md_bg_color: "red" + ''' + + + class RotateBox(ButtonBehavior, RotateBehavior, MDBoxLayout): + pass + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + def change_rotate(self, instance_button: RotateBox) -> None: + Animation(rotate_value_angle=45, d=0.3).start(instance_button) + + + Test().run() +""" + +__all__ = ("RotateBehavior",) + +from kivy.lang import Builder +from kivy.properties import ListProperty, NumericProperty + +Builder.load_string( + """ + + canvas.before: + PushMatrix + Rotate: + angle: self.rotate_value_angle + axis: tuple(self.rotate_value_axis) + origin: self.center + canvas.after: + PopMatrix +""" +) + + +class RotateBehavior: + """Base class for controlling the rotate of the widget.""" + + rotate_value_angle = NumericProperty(0) + """ + Property for getting/setting the angle of the rotation. + + :attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0`. + """ + + rotate_value_axis = ListProperty((0, 0, 1)) + """ + Property for getting/setting the axis of the rotation. + + :attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty` + and defaults to `(0, 0, 1)`. + """ diff --git a/sbapp/kivymd/uix/behaviors/scale_behavior.py b/sbapp/kivymd/uix/behaviors/scale_behavior.py new file mode 100644 index 0000000..bfc1e03 --- /dev/null +++ b/sbapp/kivymd/uix/behaviors/scale_behavior.py @@ -0,0 +1,156 @@ +""" +Behaviors/Scale +=============== + +.. versionadded:: 1.1.0 + +Base class for controlling the scale of the widget. + +.. note:: See `kivy.graphics.Rotate + `_ + for more information. + +Kivy +---- + +.. code-block:: python + + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.properties import NumericProperty + from kivy.uix.button import Button + from kivy.app import App + + + KV = ''' + Screen: + + ScaleButton: + size_hint: .5, .5 + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.change_scale(self) + + canvas.before: + PushMatrix + Scale: + x: self.scale_value_x + y: self.scale_value_y + z: self.scale_value_x + origin: self.center + canvas.after: + PopMatrix + ''' + + + class ScaleButton(Button): + scale_value_x = NumericProperty(1) + scale_value_y = NumericProperty(1) + scale_value_z = NumericProperty(1) + + + class Test(App): + def build(self): + return Builder.load_string(KV) + + def change_scale(self, instance_button: Button) -> None: + Animation( + scale_value_x=0.5, + scale_value_y=0.5, + scale_value_z=0.5, + d=0.3, + ).start(instance_button) + + + Test().run() + +KivyMD +------ + +.. code-block:: python + + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.uix.behaviors import ButtonBehavior + + from kivymd.app import MDApp + from kivymd.uix.behaviors import ScaleBehavior + from kivymd.uix.boxlayout import MDBoxLayout + + KV = ''' + MDScreen: + + ScaleBox: + size_hint: .5, .5 + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.change_scale(self) + md_bg_color: "red" + ''' + + + class ScaleBox(ButtonBehavior, ScaleBehavior, MDBoxLayout): + pass + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + def change_scale(self, instance_button: ScaleBox) -> None: + Animation( + scale_value_x=0.5, + scale_value_y=0.5, + scale_value_z=0.5, + d=0.3, + ).start(instance_button) + + + Test().run() +""" + +__all__ = ("ScaleBehavior",) + +from kivy.lang import Builder +from kivy.properties import NumericProperty + +Builder.load_string( + """ + + canvas.before: + PushMatrix + Scale: + x: self.scale_value_x + y: self.scale_value_y + z: self.scale_value_x + origin: self.center + canvas.after: + PopMatrix +""" +) + + +class ScaleBehavior: + """Base class for controlling the scale of the widget.""" + + scale_value_x = NumericProperty(1) + """ + X-axis value. + + :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + scale_value_y = NumericProperty(1) + """ + Y-axis value. + + :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + scale_value_z = NumericProperty(1) + """ + Z-axis value. + + :attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ diff --git a/sbapp/kivymd/uix/behaviors/stencil_behavior.py b/sbapp/kivymd/uix/behaviors/stencil_behavior.py new file mode 100644 index 0000000..4bb5f6d --- /dev/null +++ b/sbapp/kivymd/uix/behaviors/stencil_behavior.py @@ -0,0 +1,134 @@ +""" +Behaviors/Stencil +================= + +.. versionadded:: 1.1.0 + +Base class for controlling the stencil instructions of the widget. + +.. note:: See `Stencil instructions + `_ + for more information. + +Kivy +---- + +.. code-block:: python + + from kivy.lang import Builder + from kivy.app import App + + KV = ''' + Carousel: + + Button: + size_hint: .9, .8 + pos_hint: {"center_x": .5, "center_y": .5} + + canvas.before: + StencilPush + RoundedRectangle: + pos: root.pos + size: root.size + StencilUse + canvas.after: + StencilUnUse + RoundedRectangle: + pos: root.pos + size: root.size + StencilPop + ''' + + + class Test(App): + def build(self): + return Builder.load_string(KV) + + + Test().run() + +KivyMD +------ + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.behaviors import StencilBehavior + from kivymd.uix.fitimage import FitImage + + KV = ''' + #:import os os + #:import images_path kivymd.images_path + + + MDCarousel: + + StencilImage: + size_hint: .9, .8 + pos_hint: {"center_x": .5, "center_y": .5} + source: os.path.join(images_path, "logo", "kivymd-icon-512.png") + ''' + + + class StencilImage(FitImage, StencilBehavior): + pass + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + + Test().run() +""" + +__all__ = ("StencilBehavior",) + +from kivy.lang import Builder +from kivy.properties import VariableListProperty + +Builder.load_string( + """ + + canvas.before: + StencilPush + RoundedRectangle: + pos: root.pos + size: root.size + # FIXME: Sometimes the radius has the value [], which get a + # `GraphicException: Invalid radius value, must be list of tuples/numerics` error + radius: root.radius if root.radius else [0, 0, 0, 0] + StencilUse + canvas.after: + StencilUnUse + RoundedRectangle: + pos: root.pos + size: root.size + # FIXME: Sometimes the radius has the value [], which get a + # `GraphicException: Invalid radius value, must be list of tuples/numerics` error + radius: root.radius if root.radius else [0, 0, 0, 0] + StencilPop +""" +) + + +class StencilBehavior: + """Base class for controlling the stencil instructions of the widget.""" + + radius = VariableListProperty([0], length=4) + """ + Canvas radius. + + .. versionadded:: 1.0.0 + + .. code-block:: python + + # Top left corner slice. + MDWidget: + radius: [25, 0, 0, 0] + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ diff --git a/sbapp/kivymd/uix/behaviors/toggle_behavior.py b/sbapp/kivymd/uix/behaviors/toggle_behavior.py index 054849c..c396a29 100644 --- a/sbapp/kivymd/uix/behaviors/toggle_behavior.py +++ b/sbapp/kivymd/uix/behaviors/toggle_behavior.py @@ -14,61 +14,104 @@ example: pass -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.behaviors.toggle_behavior import MDToggleButton - from kivymd.uix.button import MDRectangleFlatButton + .. code-block:: python - KV = ''' - Screen: + from kivy.lang import Builder - MDBoxLayout: - adaptive_size: True - pos_hint: {"center_x": .5, "center_y": .5} + from kivymd.app import MDApp + from kivymd.uix.behaviors.toggle_behavior import MDToggleButton + from kivymd.uix.button import MDFlatButton - MyToggleButton: - text: "Show ads" - group: "x" + KV = ''' + MDScreen: - MyToggleButton: - text: "Do not show ads" - group: "x" + MDBoxLayout: + adaptive_size: True + spacing: "12dp" + pos_hint: {"center_x": .5, "center_y": .5} - MyToggleButton: - text: "Does not matter" - group: "x" - ''' + MyToggleButton: + text: "Show ads" + group: "x" + + MyToggleButton: + text: "Do not show ads" + group: "x" + + MyToggleButton: + text: "Does not matter" + group: "x" + ''' - class MyToggleButton(MDRectangleFlatButton, MDToggleButton): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.background_down = self.theme_cls.primary_light + class MyToggleButton(MDFlatButton, MDToggleButton): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.background_down = self.theme_cls.primary_color - class Test(MDApp): - def build(self): - return Builder.load_string(KV) + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - Test().run() + Test().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.behaviors.toggle_behavior import MDToggleButton + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDFlatButton + from kivymd.uix.screen import MDScreen + + + class MyToggleButton(MDFlatButton, MDToggleButton): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.background_down = self.theme_cls.primary_color + + + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDBoxLayout( + MyToggleButton( + text="Show ads", + group="x", + ), + MyToggleButton( + text="Do not show ads", + group="x", + ), + MyToggleButton( + text="Does not matter", + group="x", + ), + adaptive_size=True, + spacing="12dp", + pos_hint={"center_x": .5, "center_y": .5}, + ), + ) + ) + + + Test().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif :align: center -.. code-block:: python - - class MyToggleButton(MDFillRoundFlatButton, MDToggleButton): - def __init__(self, **kwargs): - self.background_down = MDApp.get_running_app().theme_cls.primary_dark - super().__init__(**kwargs) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif - :align: center - You can inherit the ``MyToggleButton`` class only from the following classes ---------------------------------------------------------------------------- @@ -88,6 +131,7 @@ from kivy.properties import BooleanProperty, ColorProperty from kivy.uix.behaviors import ToggleButtonBehavior from kivymd.uix.button import ( + ButtonContentsIconText, MDFillRoundFlatButton, MDFillRoundFlatIconButton, MDFlatButton, @@ -149,7 +193,8 @@ class MDToggleButton(ToggleButtonBehavior): # Do the object inherited from the "supported" buttons? if not issubclass(self.__class__, classinfo): raise ValueError( - f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}" + f"Class {self.__class__} must be inherited from one of the " + f"classes in the list {classinfo}" ) if ( not self.background_normal @@ -165,10 +210,12 @@ class MDToggleButton(ToggleButtonBehavior): ): self.__is_filled = True self.background_normal = self.theme_cls.primary_color - # If not the background_normal must be the same as the inherited one: + # If not background_normal must be the same as the inherited one. else: - self.background_normal = self.md_bg_color[:] - # If no background_down is setted: + self.background_normal = ( + self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0) + ) + # If no background_down is setter. if ( not self.background_down ): # This means that if the value == [] or None will return True. @@ -200,3 +247,6 @@ class MDToggleButton(ToggleButtonBehavior): ): # If the background is transparent, the font color must be the # primary color. self.text_color = self.font_color_normal + + if issubclass(self.__class__, ButtonContentsIconText): + self.icon_color = self.text_color diff --git a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv index 46c29f5..79f726b 100644 --- a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv +++ b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv @@ -1,4 +1,3 @@ -#:import sm kivy.uix.screenmanager #:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT @@ -9,7 +8,7 @@ ScreenManager: id: tab_manager - transition: sm.FadeTransition(duration=.2) + transition: root.transition(duration=root.transition_duration) on_current: root.dispatch( \ "on_switch_tabs", \ @@ -96,7 +95,7 @@ radius: [16,] size: root._selected_region_width, dp(32) pos: - self.center_x - self.width - dp(8), \ + self.center_x - root._selected_region_width / 2, \ self.center_y - (dp(16)) MDLabel: diff --git a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py index a3a77bc..d99f54b 100755 --- a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py +++ b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py @@ -62,59 +62,120 @@ For ease of understanding, this code works like this: Example ------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp - class Test(MDApp): + class Test(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - return Builder.load_string( - ''' - MDScreen: + def build(self): + self.theme_cls.material_style = "M3" + self.theme_cls.theme_style = "Dark" + return Builder.load_string( + ''' + MDScreen: - MDBottomNavigation: - panel_color: "#eeeaea" - selected_color_background: "#97ecf8" - text_color_active: 0, 0, 0, 1 + MDBottomNavigation: + #panel_color: "#eeeaea" + selected_color_background: "orange" + text_color_active: "lightgrey" - MDBottomNavigationItem: - name: 'screen 1' - text: 'Mail' - icon: 'gmail' - badge_icon: "numeric-10" + MDBottomNavigationItem: + name: 'screen 1' + text: 'Mail' + icon: 'gmail' + badge_icon: "numeric-10" - MDLabel: - text: 'Mail' - halign: 'center' + MDLabel: + text: 'Mail' + halign: 'center' - MDBottomNavigationItem: - name: 'screen 2' - text: 'Discord' - icon: 'discord' - badge_icon: "numeric-5" + MDBottomNavigationItem: + name: 'screen 2' + text: 'Twitter' + icon: 'twitter' + badge_icon: "numeric-5" - MDLabel: - text: 'Discord' - halign: 'center' + MDLabel: + text: 'Twitter' + halign: 'center' - MDBottomNavigationItem: - name: 'screen 3' - text: 'LinkedIN' - icon: 'linkedin' + MDBottomNavigationItem: + name: 'screen 3' + text: 'LinkedIN' + icon: 'linkedin' - MDLabel: - text: 'LinkedIN' - halign: 'center' - ''' - ) + MDLabel: + text: 'LinkedIN' + halign: 'center' + ''' + ) - Test().run() + Test().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + + class Test(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + self.theme_cls.theme_style = "Dark" + return ( + MDScreen( + MDBottomNavigation( + MDBottomNavigationItem( + MDLabel( + text='Mail', + halign='center', + ), + name='screen 1', + text='Mail', + icon='gmail', + badge_icon="numeric-10", + ), + MDBottomNavigationItem( + MDLabel( + text='Twitter', + halign='center', + ), + name='screen 1', + text='Twitter', + icon='twitter', + badge_icon="numeric-10", + ), + MDBottomNavigationItem( + MDLabel( + text='LinkedIN', + halign='center', + ), + name='screen 1', + text='LinkedIN', + icon='linkedin', + badge_icon="numeric-10", + ), + selected_color_background="orange", + text_color_active="lightgrey", + ) + ) + ) + + + Test().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif :align: center @@ -192,13 +253,13 @@ from kivy.properties import ( ) from kivy.uix.behaviors import ButtonBehavior from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import ScreenManagerException +from kivy.uix.screenmanager import FadeTransition, ScreenManagerException from kivymd import uix_path from kivymd.material_resources import STANDARD_INCREMENT from kivymd.theming import ThemableBehavior, ThemeManager from kivymd.uix.anchorlayout import MDAnchorLayout -from kivymd.uix.behaviors import FakeRectangularElevationBehavior +from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior from kivymd.uix.behaviors.backgroundcolor_behavior import ( SpecificBackgroundColorBehavior, ) @@ -364,8 +425,8 @@ class MDTab(MDScreen, ThemableBehavior): and defaults to `''`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.index = 0 self.parent_widget = None self.register_event_type("on_tab_touch_down") @@ -407,6 +468,31 @@ class MDBottomNavigationItem(MDTab): and defaults to `None`. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def animate_header( + self, bottom_navigation_object, bottom_navigation_header_object + ) -> None: + if bottom_navigation_object.use_text: + Animation(_label_font_size=sp(12), d=0.1).start( + bottom_navigation_object.previous_tab.header + ) + Animation( + _selected_region_width=0, + t="in_out_sine", + d=0, + ).start(bottom_navigation_header_object) + Animation( + _text_color_normal=bottom_navigation_header_object.text_color_normal + if bottom_navigation_object.previous_tab.header.text_color_normal + != [1, 1, 1, 1] + else self.theme_cls.disabled_hint_text_color, + d=0.1, + ).start(bottom_navigation_object.previous_tab.header) + bottom_navigation_object.previous_tab.header.active = False + self.header.active = True + def on_tab_press(self, *args) -> None: """Called when clicking on a panel item.""" @@ -414,28 +500,13 @@ class MDBottomNavigationItem(MDTab): bottom_navigation_header_object = ( bottom_navigation_object.previous_tab.header ) - bottom_navigation_object.ids.tab_manager.current = self.name if bottom_navigation_object.previous_tab is not self: - if bottom_navigation_object.use_text: - Animation(_label_font_size=sp(12), d=0.1).start( - bottom_navigation_object.previous_tab.header - ) - Animation( - _selected_region_width=0, - t="in_out_sine", - d=0, - ).start(bottom_navigation_header_object) - Animation( - _text_color_normal=bottom_navigation_header_object.text_color_normal - if bottom_navigation_object.previous_tab.header.text_color_normal - != [1, 1, 1, 1] - else self.theme_cls.disabled_hint_text_color, - d=0.1, - ).start(bottom_navigation_object.previous_tab.header) - bottom_navigation_object.previous_tab.header.active = False - self.header.active = True - bottom_navigation_object.previous_tab = self + self.animate_header( + bottom_navigation_object, bottom_navigation_header_object + ) + + super().on_tab_press(*args) def on_disabled( self, instance_bottom_navigation_item, disabled_value: bool @@ -463,7 +534,7 @@ class TabbedPanelBase( and defaults to `None`. """ - previous_tab = ObjectProperty() + previous_tab = ObjectProperty(None, aloownone=True) """ :attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`. """ @@ -479,7 +550,7 @@ class TabbedPanelBase( tabs = ListProperty() -class MDBottomNavigation(TabbedPanelBase): +class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase): """ A bottom navigation that is implemented by delegating all items to a :class:`~kivy.uix.screenmanager.ScreenManager`. @@ -492,6 +563,26 @@ class MDBottomNavigation(TabbedPanelBase): .. versionadded:: 1.0.0 """ + transition = ObjectProperty(FadeTransition) + """ + Transition animation of bottom navigation screen manager. + + .. versionadded:: 1.1.0 + + :attr:`transition` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `FadeTransition`. + """ + + transition_duration = NumericProperty(0.2) + """ + Duration animation of bottom navigation screen manager. + + .. versionadded:: 1.1.0 + + :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + text_color_normal = ColorProperty([1, 1, 1, 1]) """ Text color of the label when it is not selected. @@ -594,14 +685,15 @@ class MDBottomNavigation(TabbedPanelBase): and defaults to `False`. """ + widget_index = NumericProperty(0) + # Text active color if it is selected. _active_color = ColorProperty([1, 1, 1, 1]) - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_switch_tabs") + def __init__(self, *args, **kwargs): self.previous_tab = None - self.widget_index = 0 + self.register_event_type("on_switch_tabs") + super().__init__(*args, **kwargs) self.theme_cls.bind(material_style=self.refresh_tabs) Window.bind(on_resize=self.on_resize) Clock.schedule_once(lambda x: self.on_resize()) @@ -660,8 +752,11 @@ class MDBottomNavigation(TabbedPanelBase): def on_selected_color_background( self, instance_bottom_navigation, color: list ) -> None: - for tab in self.ids.tab_bar.children: - tab.selected_color_background = color + def on_selected_color_background(*args): + for tab in self.ids.tab_bar.children: + tab.selected_color_background = color + + Clock.schedule_once(on_selected_color_background) def on_use_text( self, instance_bottom_navigation, use_text_value: bool @@ -698,12 +793,15 @@ class MDBottomNavigation(TabbedPanelBase): def on_text_color_active( self, instance_bottom_navigation, color: list ) -> None: - MDBottomNavigationHeader.text_color_active = color - self.text_color_active = color - for tab in self.ids.tab_bar.children: - tab.text_color_active = color - if tab.active: - tab._text_color_normal = color + def on_text_color_active(*args): + MDBottomNavigationHeader.text_color_active = color + self.text_color_active = color + for tab in self.ids.tab_bar.children: + tab.text_color_active = color + if tab.active: + tab._text_color_normal = color + + Clock.schedule_once(on_text_color_active) def on_switch_tabs(self, bottom_navigation_item, name_tab: str) -> None: """ @@ -759,8 +857,6 @@ class MDBottomNavigation(TabbedPanelBase): class MDBottomNavigationBar( - ThemableBehavior, - FakeRectangularElevationBehavior, - MDFloatLayout, + ThemableBehavior, CommonElevationBehavior, MDFloatLayout ): pass diff --git a/sbapp/kivymd/uix/bottomsheet/bottomsheet.py b/sbapp/kivymd/uix/bottomsheet/bottomsheet.py index bf7e98f..522f31b 100755 --- a/sbapp/kivymd/uix/bottomsheet/bottomsheet.py +++ b/sbapp/kivymd/uix/bottomsheet/bottomsheet.py @@ -34,7 +34,7 @@ Usage :class:`~MDListBottomSheet` MDTopAppBar: title: "Example BottomSheet" pos_hint: {"top": 1} - elevation: 10 + elevation: 4 MDRaisedButton: text: "Open list bottom sheet" @@ -94,7 +94,7 @@ which will be used as an icon to the left of the item: MDTopAppBar: title: 'Example BottomSheet' pos_hint: {"top": 1} - elevation: 10 + elevation: 4 MDRaisedButton: text: "Open grid bottom sheet" @@ -180,7 +180,7 @@ which will be used as an icon to the left of the item: MDTopAppBar: title: 'Example BottomSheet' pos_hint: {"top": 1} - elevation: 10 + elevation: 4 MDRaisedButton: text: "Open custom bottom sheet" diff --git a/sbapp/kivymd/uix/boxlayout.py b/sbapp/kivymd/uix/boxlayout.py index b5e0a52..6922de7 100644 --- a/sbapp/kivymd/uix/boxlayout.py +++ b/sbapp/kivymd/uix/boxlayout.py @@ -8,7 +8,7 @@ with some widget properties. For example: BoxLayout --------- -.. code-block:: +.. code-block:: kv BoxLayout: size_hint_y: None @@ -24,7 +24,7 @@ BoxLayout MDBoxLayout ----------- -.. code-block:: +.. code-block:: kv MDBoxLayout: adaptive_height: True @@ -88,7 +88,13 @@ __all__ = ("MDBoxLayout",) from kivy.uix.boxlayout import BoxLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDBoxLayout(BoxLayout, MDAdaptiveWidget): - pass +class MDBoxLayout(DeclarativeBehavior, BoxLayout, MDAdaptiveWidget): + """ + Box layout class. + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/button/__init__.py b/sbapp/kivymd/uix/button/__init__.py index dddf186..b833a4c 100644 --- a/sbapp/kivymd/uix/button/__init__.py +++ b/sbapp/kivymd/uix/button/__init__.py @@ -1,6 +1,7 @@ # NOQA F401 from .button import ( BaseButton, + ButtonContentsIconText, MDFillRoundFlatButton, MDFillRoundFlatIconButton, MDFlatButton, diff --git a/sbapp/kivymd/uix/button/button.kv b/sbapp/kivymd/uix/button/button.kv index c7523a4..4cee339 100644 --- a/sbapp/kivymd/uix/button/button.kv +++ b/sbapp/kivymd/uix/button/button.kv @@ -3,9 +3,9 @@ Clear Color: rgba: - (self._md_bg_color or [0.0, 0.0, 0.0, 0.0]) \ + self._md_bg_color \ if not self.disabled else \ - (self._md_bg_color_disabled or [0.0, 0.0, 0.0, 0.0]) + self._md_bg_color_disabled RoundedRectangle: size: self.size pos: self.pos @@ -13,19 +13,17 @@ radius: [root._radius, ] Color: rgba: - root._line_color or [0.0, 0.0, 0.0, 0.0] \ + root._line_color \ if not root.disabled else \ - ( \ - root._line_color_disabled \ - or self._disabled_color \ - or [0.0, 0.0, 0.0, 0.0] \ - ) + (root._line_color_disabled or self._disabled_color) Line: width: root.line_width rounded_rectangle: - (self.x, self.y, self.width, self.height, \ + ( \ + self.x, self.y, self.width, self.height, \ root._radius, root._radius, root._radius, root._radius, \ - self.height) + self.height \ + ) size_hint: None, None anchor_x: root.halign @@ -33,21 +31,28 @@ _round_rad: [self._radius] * 4 - lbl_txt: lbl_txt width: - max(root._min_width, \ - root.padding[0] + lbl_txt.texture_size[0] + root.padding[2]) + max( \ + root._min_width, \ + root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \ + ) size_hint_min_x: - max(root._min_width, \ - root.padding[0] + lbl_txt.texture_size[0] + root.padding[2]) + max( \ + root._min_width, \ + root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \ + ) height: - max(root._min_height, \ - root.padding[1] + lbl_txt.texture_size[1] + root.padding[3]) + max( \ + root._min_height, \ + root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \ + ) size_hint_min_y: - max(root._min_height, \ - root.padding[1] + lbl_txt.texture_size[1] + root.padding[3]) + max( \ + root._min_height, \ + root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \ + ) MDLabel: id: lbl_txt @@ -84,7 +89,10 @@ # This is only a temporary fix and does not fix the cause of the error. (root._icon_color if root._icon_color else root.theme_cls.text_color) \ if not root.disabled else \ - root.theme_cls.disabled_hint_text_color + root.theme_cls.disabled_hint_text_color \ + if not root.disabled_color else \ + root.disabled_color + on_icon: if self.icon not in md_icons.keys(): self.size_hint = (1, 1) theme_text_color: root._theme_icon_color @@ -131,7 +139,7 @@ id: box adaptive_size: True padding: 0 - spacing: "4dp" + spacing: "8dp" MDIcon: id: lbl_ic @@ -193,48 +201,21 @@ radius: [self.height / 2] - + theme_text_color: "Custom" md_bg_color: self.theme_cls.primary_color + + + padding_x: "8dp" + padding_y: "8dp" + adaptive_size: True + theme_text_color: "Custom" + canvas.before: - PushMatrix - Rotate: - angle: self._angle - axis: (0, 0, 1) - origin: self.center - canvas.after: - PopMatrix - - -# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead -# :class:`~kivy.uix.boxlayout.BoxLayout`. - - size_hint: None, None - padding: "8dp", "4dp", "8dp", "4dp" - height: label.texture_size[1] + self.padding[1] * 2 - width: label.texture_size[0] + self.padding[0] * 2 - elevation: 10 - - # TODO: Use `md_bg_color` and `radius` instead `canvasю - canvas: Color: - rgba: - self.theme_cls.primary_color \ - if not root.bg_color else \ - root.bg_color + rgba: self.bg_color RoundedRectangle: - pos: self.pos size: self.size - radius: [5] - - Label: - id: label - markup: True - text: root.text - size_hint: None, None - size: self.texture_size - color: - root.theme_cls.text_color \ - if not root.text_color else \ - root.text_color + pos: self.pos + radius: self.radius diff --git a/sbapp/kivymd/uix/button/button.py b/sbapp/kivymd/uix/button/button.py index ddfa81e..3a96831 100755 --- a/sbapp/kivymd/uix/button/button.py +++ b/sbapp/kivymd/uix/button/button.py @@ -33,31 +33,62 @@ Components/Button MDIconButton ------------ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.gif +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDIconButton: + icon: "language-python" + pos_hint: {"center_x": .5, "center_y": .5} + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDIconButton + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDIconButton( + icon="language-python", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.png :align: center -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDIconButton: - icon: "language-python" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - The :class:`~MDIconButton.icon` parameter must have the name of the icon from ``kivymd/icon_definitions.py`` file. @@ -66,9 +97,9 @@ You can also use custom icons: .. code-block:: kv MDIconButton: - icon: "data/logo/kivy-icon-256.png" + icon: "kivymd/images/logo/kivymd-icon-256.png" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.png :align: center By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``. @@ -80,7 +111,7 @@ Use :class:`~BaseButton.icon_size` attribute to resize the button: icon: "android" icon_size: "64sp" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.png :align: center By default, the color of :class:`~MDIconButton` @@ -88,8 +119,6 @@ By default, the color of :class:`~MDIconButton` You can change the color of :class:`~MDIconButton` as the text color of :class:`~kivymd.uix.label.MDLabel`, substituting ``theme_icon_color`` for ``theme_text_color`` and ``icon_color`` for ``text_color``. -The use of ``user_font_size``, ``text_color`` and ``theme_text_color`` for -:class:`~MDIconButton` is deprecated. .. code-block:: kv @@ -145,8 +174,10 @@ Material design style 3 ''' - class TestNavigationDrawer(MDApp): + class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" self.theme_cls.material_style = "M3" return Builder.load_string(KV) @@ -168,26 +199,23 @@ Material design style 3 ) - TestNavigationDrawer().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.png :align: center .. MDFlatButton: MDFlatButton ------------ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button.gif - :align: center - To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter: .. code-block:: kv MDFlatButton: - text: "MDFLATBUTTON" + text: "MDFlatButton" theme_text_color: "Custom" - text_color: 0, 0, 1, 1 + text_color: "orange" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png :align: center @@ -197,7 +225,7 @@ Or use markup: .. code-block:: kv MDFlatButton: - text: "[color=#00ffcc]MDFLATBUTTON[/color]" + text: "[color=#00ffcc]MDFlatButton[/color]" To specify the font size and font name, use the parameters as in the usual `Kivy` buttons: @@ -205,7 +233,7 @@ To specify the font size and font name, use the parameters as in the usual .. code-block:: kv MDFlatButton: - text: "MDFLATBUTTON" + text: "MDFlatButton" font_size: "18sp" font_name: "path/to/font" @@ -213,33 +241,29 @@ To specify the font size and font name, use the parameters as in the usual MDRaisedButton -------------- -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.gif - :align: center - This button is similar to the :class:`~MDFlatButton` button except that you can set the background color for :class:`~MDRaisedButton`: .. code-block:: kv MDRaisedButton: - text: "MDRAISEDBUTTON" - md_bg_color: 1, 0, 1, 1 + text: "MDRaisedButton" + md_bg_color: "red" +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.png + :align: center .. MDRectangleFlatButton: MDRectangleFlatButton --------------------- -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button.gif - :align: center - .. code-block:: kv MDRectangleFlatButton: - text: "MDRECTANGLEFLATBUTTON" + text: "MDRectangleFlatButton" theme_text_color: "Custom" - text_color: 1, 0, 0, 1 - line_color: 0, 0, 1, 1 + text_color: "white" + line_color: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png :align: center @@ -248,9 +272,6 @@ MDRectangleFlatButton MDRectangleFlatIconButton ------------------------- -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button.png - :align: center - Button parameters :class:`~MDRectangleFlatIconButton` are the same as button :class:`~MDRectangleFlatButton`, with the addition of the ``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`. @@ -259,12 +280,12 @@ button :class:`~MDRectangleFlatButton`, with the addition of the MDRectangleFlatIconButton: icon: "android" - text: "MDRECTANGLEFLATICONBUTTON" + text: "MDRectangleFlatIconButton" theme_text_color: "Custom" - text_color: 0, 0, 1, 1 - line_color: 1, 0, 1, 1 + text_color: "white" + line_color: "red" theme_icon_color: "Custom" - icon_color: 1, 0, 0, 1 + icon_color: "orange" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png :align: center @@ -281,16 +302,18 @@ Without border class Example(MDApp): def build(self): - screen = MDScreen() - screen.add_widget( - MDRectangleFlatIconButton( - text="MDRectangleFlatIconButton", - icon="language-python", - line_color=(0, 0, 0, 0), - pos_hint={"center_x": .5, "center_y": .5}, + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDRectangleFlatIconButton( + text="MDRectangleFlatIconButton", + icon="language-python", + line_color=(0, 0, 0, 0), + pos_hint={"center_x": .5, "center_y": .5}, + ) ) ) - return screen Example().run() @@ -303,6 +326,9 @@ Without border line_color: 0, 0, 0, 0 pos_hint: {"center_x": .5, "center_y": .5} +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-without-border.png + :align: center + .. MDRoundFlatButton: MDRoundFlatButton ----------------- @@ -310,8 +336,8 @@ MDRoundFlatButton .. code-block:: kv MDRoundFlatButton: - text: "MDROUNDFLATBUTTON" - text_color: 0, 1, 0, 1 + text: "MDRoundFlatButton" + text_color: "white" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png :align: center @@ -320,9 +346,6 @@ MDRoundFlatButton MDRoundFlatIconButton --------------------- -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png - :align: center - Button parameters :class:`~MDRoundFlatIconButton` are the same as button :class:`~MDRoundFlatButton`, with the addition of the ``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`: @@ -330,8 +353,12 @@ button :class:`~MDRoundFlatButton`, with the addition of the .. code-block:: kv MDRoundFlatIconButton: + text: "MDRoundFlatIconButton" icon: "android" - text: "MDROUNDFLATICONBUTTON" + text_color: "white" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png + :align: center .. MDFillRoundFlatButton: MDFillRoundFlatButton @@ -361,14 +388,14 @@ button :class:`~MDRaisedButton`, with the addition of the MDTextButton ------------ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png - :align: center - .. code-block:: kv MDTextButton: - text: "MDTEXTBUTTON" - custom_color: 0, 1, 0, 1 + text: "MDTextButton" + custom_color: "white" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png + :align: center .. MDFloatingActionButtonSpeedDial: MDFloatingActionButtonSpeedDial @@ -400,6 +427,8 @@ MDFloatingActionButtonSpeedDial } def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) @@ -410,30 +439,65 @@ MDFloatingActionButtonSpeedDial Or without KV Language: -.. code-block:: python +.. tabs:: - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButtonSpeedDial + .. tab:: Imperative python style + + .. code-block:: python + + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp + from kivymd.uix.button import MDFloatingActionButtonSpeedDial - class Example(MDApp): - data = { - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - } + class Example(MDApp): + data = { + 'Python': 'language-python', + 'PHP': 'language-php', + 'C++': 'language-cpp', + } - def build(self): - screen = MDScreen() - speed_dial = MDFloatingActionButtonSpeedDial() - speed_dial.data = self.data - speed_dial.root_button_anim = True - screen.add_widget(speed_dial) - return screen + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + screen = MDScreen() + speed_dial = MDFloatingActionButtonSpeedDial() + speed_dial.data = self.data + speed_dial.root_button_anim = True + screen.add_widget(speed_dial) + return screen - Example().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp + from kivymd.uix.button import MDFloatingActionButtonSpeedDial + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDFloatingActionButtonSpeedDial( + data={ + 'Python': 'language-python', + 'PHP': 'language-php', + 'C++': 'language-cpp', + }, + root_button_anim=True, + ) + ) + ) + + + Example().run() You can use various types of animation of labels for buttons on the stack: @@ -445,21 +509,133 @@ You can use various types of animation of labels for buttons on the stack: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif :align: center -You can set your color values ​​for background, text of buttons etc: +You can set your color values for background, text of buttons etc: .. code-block:: kv MDFloatingActionButtonSpeedDial: - bg_hint_color: app.theme_cls.primary_light + hint_animation: True + bg_hint_color: app.theme_cls.primary_dark .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png :align: center -.. seealso:: +Binds to individual buttons +--------------------------- - `See full example `_ +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: python + + from kivy.lang import Builder + from kivy.properties import DictProperty + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDFloatingActionButtonSpeedDial: + id: speed_dial + data: app.data + root_button_anim: True + hint_animation: True + ''' + + + class Example(MDApp): + data = DictProperty() + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + self.data = { + 'Python': 'language-python', + 'JS': [ + 'language-javascript', + "on_press", lambda x: print("pressed JS"), + "on_release", lambda x: print( + "stack_buttons", + self.root.ids.speed_dial.stack_buttons + ) + ], + 'PHP': [ + 'language-php', + "on_press", lambda x: print("pressed PHP"), + "on_release", self.callback + ], + 'C++': [ + 'language-cpp', + "on_press", lambda x: print("pressed C++"), + "on_release", lambda x: self.callback() + ], + } + return Builder.load_string(KV) + + def callback(self, *args): + print(args) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDFloatingActionButtonSpeedDial + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDFloatingActionButtonSpeedDial( + id="speed_dial", + hint_animation=True, + root_button_anim=True, + ) + ) + ) + + def on_start(self): + data = { + "Python": "language-python", + "JS": [ + "language-javascript", + "on_press", lambda x: print("pressed JS"), + "on_release", lambda x: print( + "stack_buttons", + self.root.ids.speed_dial.stack_buttons + ) + ], + "PHP": [ + "language-php", + "on_press", lambda x: print("pressed PHP"), + "on_release", self.callback + ], + "C++": [ + "language-cpp", + "on_press", lambda x: print("pressed C++"), + "on_release", lambda x: self.callback() + ], + } + self.root.ids.speed_dial.data = data + + def callback(self, *args): + print(args) + + + Example().run() """ +from __future__ import annotations + __all__ = ( "BaseButton", "MDIconButton", @@ -497,8 +673,8 @@ from kivy.properties import ( ) from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout from kivy.uix.floatlayout import FloatLayout +from kivy.weakproxy import WeakProxy from kivymd import uix_path from kivymd.color_definitions import text_colors @@ -506,9 +682,9 @@ from kivymd.font_definitions import theme_font_styles from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( CommonElevationBehavior, - FakeRectangularElevationBehavior, + DeclarativeBehavior, RectangularRippleBehavior, - RoundedRectangularElevationBehavior, + RotateBehavior, ) from kivymd.uix.label import MDLabel from kivymd.uix.tooltip import MDTooltip @@ -528,11 +704,76 @@ theme_text_color_options = ( "ContrastParentBackground", ) +# FIXME: If you set a new elevation value for the button +# (press the "Set elevation" button), then disable the button +# (press the "Disabled" button), and then enable the button +# (press the "Undisabled" button), then the previously set elevation value is +# reset to zero. +# In addition, if you set a new elevation value +# (press the "Set elevation" button) and click on the button for which we set +# the elevation value, then the new elevation value will receive the previous +# elevation value. This problem is only related to the buttons. +# For example, there is no such problem for the MDCard widget. + +""" +from kivy.lang import Builder + +from kivymd.app import MDApp + +KV = ''' +MDScreen: + + MDRaisedButton: + size_hint: .5, .5 + id: button + pos_hint: {"center_x": .5, "center_y": .5} + elevation: 0 + + MDBoxLayout: + adaptive_size: True + pos_hint: {"center_x": .5} + spacing: 12 + padding: 12 + + MDRaisedButton: + text: "Set elevation" + pos_hint: {"center_x": .5, "bottom": 1} + on_release: button.elevation = 4 + + MDRaisedButton: + text: "Disabled" + pos_hint: {"center_x": .5, "bottom": 1} + on_release: button.disabled = True + + MDRaisedButton: + text: "Undisabled" + pos_hint: {"center_x": .5, "bottom": 1} + on_release: button.disabled = False +''' + + +class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + +Test().run() +""" + class BaseButton( - RectangularRippleBehavior, ThemableBehavior, ButtonBehavior, AnchorLayout + DeclarativeBehavior, + RectangularRippleBehavior, + ThemableBehavior, + ButtonBehavior, + AnchorLayout, ): - """Base class for all buttons.""" + """ + Base class for all buttons. + + For more information, see in the + :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation. + """ padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)]) """ @@ -618,7 +859,7 @@ class BaseButton( text_color = ColorProperty(None) """ - Button text color in (r, g, b, a) format. + Button text color in (r, g, b, a) or string format. :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -626,7 +867,7 @@ class BaseButton( icon_color = ColorProperty(None) """ - Button icon color in (r, g, b, a) format. + Button icon color in (r, g, b, a) or string format. :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -659,18 +900,6 @@ class BaseButton( and defaults to `None`. """ - user_font_size = NumericProperty(0, deprecated=True) - """ - Custom font size for :class:`~MDIconButton`. - - .. deprecated in 1.0.0:: - - Use :attr:`icon_size` instead. - - :attr:`user_font_size` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - line_width = NumericProperty(1) """ Line width for button border. @@ -681,7 +910,7 @@ class BaseButton( line_color = ColorProperty(None) """ - Line color for button border. + Line color in (r, g, b, a) or string format for button border. :attr:`line_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -689,7 +918,7 @@ class BaseButton( line_color_disabled = ColorProperty(None) """ - Disabled line color for button border. + Disabled line color in (r, g, b, a) or string format for button border. .. versionadded:: 1.0.0 @@ -699,7 +928,7 @@ class BaseButton( md_bg_color = ColorProperty(None) """ - Button background color. + Button background color in (r, g, b, a) or string format. :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -707,7 +936,8 @@ class BaseButton( md_bg_color_disabled = ColorProperty(None) """ - The background color of the button when the button is disabled. + The background color in (r, g, b, a) or string format of the button when + the button is disabled. :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -715,8 +945,8 @@ class BaseButton( disabled_color = ColorProperty(None) """ - The color of the text and icon when the button is disabled, in the - (r, g, b, a) format. + The color of the text and icon when the button is disabled, + in (r, g, b, a) or string format. .. versionadded:: 1.0.0 @@ -737,11 +967,11 @@ class BaseButton( # Note - _radius must be > 0 to avoid rendering issues. _radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1) # Properties used for rendering. - _disabled_color = ColorProperty(None) - _md_bg_color = ColorProperty(None) - _md_bg_color_disabled = ColorProperty(None) - _line_color = ColorProperty(None) - _line_color_disabled = ColorProperty(None) + _disabled_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) + _md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) + _md_bg_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0]) + _line_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) + _line_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0]) _theme_text_color = OptionProperty(None, options=theme_text_color_options) _theme_icon_color = OptionProperty(None, options=theme_text_color_options) _text_color = ColorProperty(None) @@ -763,8 +993,8 @@ class BaseButton( _animation_fade_bg = ObjectProperty(None, allownone=True) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.theme_cls.bind( primary_palette=self.set_all_colors, theme_style=self.set_all_colors, @@ -818,14 +1048,14 @@ class BaseButton( """Set all button colours (except text/icons).""" # Set main color - self._md_bg_color = ( + _md_bg_color = ( self.md_bg_color or self._default_md_bg_color or self.theme_cls.primary_color ) # Set disabled color - self._md_bg_color_disabled = ( + _md_bg_color_disabled = ( self.md_bg_color_disabled or ( [sum(self.md_bg_color[0:3]) / 3.0] * 3 @@ -838,14 +1068,14 @@ class BaseButton( ) # Set line color - self._line_color = ( + _line_color = ( self.line_color or self._default_line_color or self.theme_cls.primary_color ) # Set disabled line color - self._line_color_disabled = ( + _line_color_disabled = ( self.line_color_disabled or ( [sum(self.line_color[0:3]) / 3.0] * 3 @@ -857,6 +1087,21 @@ class BaseButton( or self.theme_cls.disabled_primary_color ) + if self.theme_cls.theme_style_switch_animation: + Animation( + _md_bg_color=_md_bg_color, + _md_bg_color_disabled=_md_bg_color_disabled, + _line_color=_line_color, + _line_color_disabled=_line_color_disabled, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self._md_bg_color = _md_bg_color + self._md_bg_color_disabled = _md_bg_color_disabled + self._line_color = _line_color + self._line_color_disabled = _line_color_disabled + def set_text_color(self, *args) -> None: """ Set _theme_text_color and _text_color based on defaults and options. @@ -881,7 +1126,9 @@ class BaseButton( """ self._theme_icon_color = ( - self.theme_icon_color or self._default_theme_icon_color + (self.theme_icon_color or self._default_theme_icon_color) + if not self.disabled + else "Custom" ) if self._default_icon_color == "PrimaryHue": default_icon_color = text_colors[self.theme_cls.primary_palette][ @@ -940,6 +1187,10 @@ class BaseButton( return super().on_touch_up(touch) def on_disabled(self, instance_button, disabled_value: bool) -> None: + if hasattr(super(), "on_disabled"): + if self.disabled is True: + Animation.cancel_all(self, "elevation") + super().on_disabled(instance_button, disabled_value) Clock.schedule_once(self.set_disabled_color) @@ -957,30 +1208,20 @@ class ButtonElevationBehaviour(CommonElevationBehavior): _elevation_raised = NumericProperty() _anim_raised = ObjectProperty(None, allownone=True) - _default_elevation = 2 + _default_elevation = 3 def __init__(self, **kwargs): + super().__init__(**kwargs) if self.elevation == 0: self.elevation = self._default_elevation - super().__init__(**kwargs) - self.bind(_radius=self.setter("radius")) - self.on_elevation(self, self.elevation) - - def on_elevation(self, instance_button, elevation_value: int) -> None: - super().on_elevation(instance_button, elevation_value) - self._elevation_raised = self.elevation + 6 + if hasattr(self, "radius"): + self.bind(_radius=self.setter("radius")) + Clock.schedule_once(self.create_anim_raised) self.on_disabled(self, self.disabled) - def on__elevation_raised( - self, instance_button, elevation_value: int - ) -> None: - Animation.cancel_all(self, "_elevation") - self._anim_raised = Animation(_elevation=self._elevation_raised, d=0.15) - - def on_disabled(self, instance_button, disabled_value: bool) -> None: - if self.disabled is True: - Animation.cancel_all(self, "_elevation") - super().on_disabled(instance_button, disabled_value) + def create_anim_raised(self, *args) -> None: + self._elevation_raised = self.elevation + 1.2 + self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15) def on_touch_down(self, touch): if not self.disabled: @@ -1003,8 +1244,8 @@ class ButtonElevationBehaviour(CommonElevationBehavior): return super().on_touch_up(touch) def stop_elevation_anim(self): - Animation.cancel_all(self, "_elevation") - self._elevation = self.elevation + Animation.cancel_all(self, "elevation") + self.elevation = self._elevation_raised - 1 class ButtonContentsText: @@ -1018,12 +1259,6 @@ class ButtonContentsIcon: _min_width = NumericProperty(0) - def __init__(self, **kwargs): - super().__init__(**kwargs) - if self.user_font_size: - self.icon_size = self.user_font_size - self.bind(user_font_size=self.setter("icon_size")) - def on_text_color(self, instance_button, color: list) -> None: """ Set icon_color equal to text_color. @@ -1074,7 +1309,7 @@ class OldButtonIconMixin: self.theme_icon_color = "Custom" -class MDFlatButton(ButtonContentsText, BaseButton): +class MDFlatButton(BaseButton, ButtonContentsText): """ A flat rectangular button with (by default) no border or background. Text is the default text color. @@ -1095,12 +1330,7 @@ class MDFlatButton(ButtonContentsText, BaseButton): """ -class MDRaisedButton( - FakeRectangularElevationBehavior, - ButtonElevationBehaviour, - ButtonContentsText, - BaseButton, -): +class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText): """ A flat button with (by default) a primary color fill and matching color text. @@ -1113,8 +1343,14 @@ class MDRaisedButton( _default_theme_text_color = "Custom" _default_text_color = "PrimaryHue" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.shadow_softness = 8 + self.shadow_offset = (0, 2) + self.shadow_radius = self._radius * 2 -class MDRectangleFlatButton(ButtonContentsText, BaseButton): + +class MDRectangleFlatButton(BaseButton, ButtonContentsText): """ A flat button with (by default) a primary color border and primary color text. @@ -1127,7 +1363,7 @@ class MDRectangleFlatButton(ButtonContentsText, BaseButton): class MDRectangleFlatIconButton( - OldButtonIconMixin, ButtonContentsIconText, BaseButton + BaseButton, OldButtonIconMixin, ButtonContentsIconText ): """ A flat button with (by default) a primary color border, primary color text @@ -1142,7 +1378,7 @@ class MDRectangleFlatIconButton( _default_icon_color = "Primary" -class MDRoundFlatButton(ButtonContentsText, BaseButton): +class MDRoundFlatButton(BaseButton, ButtonContentsText): """ A flat button with (by default) fully rounded corners, a primary color border and primary color text. @@ -1153,15 +1389,13 @@ class MDRoundFlatButton(ButtonContentsText, BaseButton): _default_theme_text_color = "Custom" _default_text_color = "Primary" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.rounded_button = True class MDRoundFlatIconButton( - OldButtonIconMixin, - ButtonContentsIconText, - BaseButton, + BaseButton, OldButtonIconMixin, ButtonContentsIconText ): """ A flat button with (by default) rounded corners, a primary color border, @@ -1175,12 +1409,12 @@ class MDRoundFlatIconButton( _default_text_color = "Primary" _default_icon_color = "Primary" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.rounded_button = True -class MDFillRoundFlatButton(ButtonContentsText, BaseButton): +class MDFillRoundFlatButton(BaseButton, ButtonContentsText): """ A flat button with (by default) rounded corners, a primary color fill and primary color text. @@ -1191,15 +1425,13 @@ class MDFillRoundFlatButton(ButtonContentsText, BaseButton): _default_theme_text_color = "Custom" _default_text_color = "PrimaryHue" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.rounded_button = True class MDFillRoundFlatIconButton( - OldButtonIconMixin, - ButtonContentsIconText, - BaseButton, + BaseButton, OldButtonIconMixin, ButtonContentsIconText ): """ A flat button with (by default) rounded corners, a primary color fill, @@ -1213,12 +1445,12 @@ class MDFillRoundFlatIconButton( _default_text_color = "PrimaryHue" _default_icon_color = "PrimaryHue" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.rounded_button = True -class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton): +class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon): """A simple rounded icon button.""" icon = StringProperty("checkbox-blank-circle") @@ -1229,39 +1461,11 @@ class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton): and defaults to `'checkbox-blank-circle'`. """ - text_color = ColorProperty(None, deprecated=True) - """ - Button icon color in (r, g, b, a) format. - - .. deprecated in 1.0.0:: - - Deprecated for :class:`~MDIconButton`. Use ``icon_color`` instead. - - :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - theme_text_color = OptionProperty( - None, options=theme_text_color_options, deprecated=True - ) - """ - Button icon type. Available options are: (`"Primary"`, `"Secondary"`, - `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`). - - .. deprecated in 1.0.0:: - - Deprecated for :class:`~MDIconButton`. Use ``theme_icon_color`` instead. - - - :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None` (set by button class). - """ - _min_width = NumericProperty(0) _default_icon_pad = max(dp(48) - sp(24), 0) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.rounded_button = True # FIXME: GraphicException: Invalid width value, must be > 0 self.line_width = 0.001 @@ -1279,11 +1483,7 @@ class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton): class MDFloatingActionButton( - OldButtonIconMixin, - RoundedRectangularElevationBehavior, - ButtonElevationBehaviour, - ButtonContentsIcon, - BaseButton, + BaseButton, OldButtonIconMixin, ButtonElevationBehaviour, ButtonContentsIcon ): """ Implementation @@ -1311,12 +1511,11 @@ class MDFloatingActionButton( _default_theme_icon_color = "Custom" _default_icon_color = "PrimaryHue" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) # FIXME: GraphicException: Invalid width value, must be > 0 self.line_width = 0.001 - self.theme_cls.bind(material_style=self.set_size) - self.theme_cls.bind(material_style=self.set__radius) + self.theme_cls.bind(material_style=self.set_size_and_radius) Clock.schedule_once(self.set_size) Clock.schedule_once(self.set__radius) Clock.schedule_once(self.set_font_size) @@ -1330,9 +1529,13 @@ class MDFloatingActionButton( def set__radius(self, *args) -> None: if self.theme_cls.material_style == "M2": + self.shadow_radius = self.height / 2 self.rounded_button = True else: + self.shadow_softness = 8 + self.shadow_offset = (0, 2) self.rounded_button = False + if self.type == "small": self._radius = dp(12) elif self.type == "standard": @@ -1340,6 +1543,12 @@ class MDFloatingActionButton( elif self.type == "large": self._radius = dp(28) + self.shadow_radius = self._radius + + def set_size_and_radius(self, *args) -> None: + self.set_size(args) + self.set__radius(args) + def set_size(self, *args) -> None: if self.theme_cls.material_style == "M2": self.size = dp(56), dp(56) @@ -1359,7 +1568,7 @@ class MDFloatingActionButton( class MDTextButton(ButtonBehavior, MDLabel): color = ColorProperty(None) """ - Button color in (r, g, b, a) format. + Button color in (r, g, b, a) or string format. :attr:`color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -1367,7 +1576,7 @@ class MDTextButton(ButtonBehavior, MDLabel): color_disabled = ColorProperty(None) """ - Button color disabled in (r, g, b, a) format. + Button color disabled in (r, g, b, a) or string format. :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -1400,14 +1609,6 @@ class MDTextButton(ButtonBehavior, MDLabel): # SpeedDial classes -class BaseFloatingRootButton(MDFloatingActionButton): - _angle = NumericProperty(0) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.elevation = 5 - - class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip): _canvas_width = NumericProperty(0) _padding_right = NumericProperty(0) @@ -1418,41 +1619,48 @@ class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip): self.height = "46dp" -# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead -# :class:`~kivy.uix.boxlayout.BoxLayout`. -class BaseFloatingLabel( - ThemableBehavior, FakeRectangularElevationBehavior, BoxLayout -): - text = StringProperty() - text_color = ColorProperty(None) - bg_color = ColorProperty(None) - - class MDFloatingBottomButton(BaseFloatingBottomButton): - pass + _bg_color = ColorProperty(None) -class MDFloatingRootButton(BaseFloatingRootButton): - pass +class MDFloatingRootButton(RotateBehavior, MDFloatingActionButton): + rotate_value_angle = NumericProperty(0) -class MDFloatingLabel(BaseFloatingLabel): - pass +class MDFloatingLabel(MDLabel): + bg_color = ColorProperty([0, 0, 0, 0]) -class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): +class MDFloatingActionButtonSpeedDial( + DeclarativeBehavior, ThemableBehavior, FloatLayout +): """ + For more information, see in the + :class:`~kivy.uix.floatlayout.FloatLayout` class documentation. + :Events: :attr:`on_open` Called when a stack is opened. :attr:`on_close` Called when a stack is closed. + :attr:`on_press_stack_button` + Called at the on_press event for the stack button. + :attr:`on_release_stack_button` + Called at the on_press event for the stack button. """ icon = StringProperty("plus") """ Root button icon name. + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + icon: "pencil" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-icon.png + :align: center + :attr:`icon` is a :class:`~kivy.properties.StringProperty` and defaults to `'plus'`. """ @@ -1465,36 +1673,59 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): and defaults to `'right'`. """ - callback = ObjectProperty(lambda x: None) + label_text_color = ColorProperty(None) """ - Custom callback. + Color of floating text labels in (r, g, b, a) or string format. .. code-block:: kv MDFloatingActionButtonSpeedDial: - callback: app.callback + label_text_color: "orange" - .. code-block:: python + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-text-color.png + :align: center - def callback(self, instance): - print(instance.icon) - - - :attr:`callback` is a :class:`~kivy.properties.ObjectProperty` + :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - label_text_color = ColorProperty([0, 0, 0, 1]) + label_bg_color = ColorProperty([0, 0, 0, 0]) """ - Floating text color in (r, g, b, a) format. + Background color of floating text labels in (r, g, b, a) or string format. - :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 1]`. + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + label_text_color: "black" + label_bg_color: "orange" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-bg-color.png + :align: center + + :attr:`label_bg_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + label_radius = VariableListProperty([0], length=4) + """ + The radius of the background of floating text labels. + + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + label_text_color: "black" + label_bg_color: "orange" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-radius.png + :align: center + + :attr:`label_radius` is a :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0]`. """ data = DictProperty() """ - Must be a dictionary + Must be a dictionary. .. code-block:: python @@ -1505,18 +1736,33 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): } """ - right_pad = BooleanProperty(True) + right_pad = BooleanProperty(False) """ - If `True`, the button will increase on the right side by 2.5 pixels - if the :attr:`~hint_animation` parameter equal to `True`. + If `True`, the background for the floating text label will increase by the + number of pixels specified in the :attr:`~right_pad_value` parameter. + + Works only if the :attr:`~hint_animation` parameter is set to `True`. .. rubric:: False + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + hint_animation: True + right_pad: False + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif :align: center .. rubric:: True + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + hint_animation: True + right_pad: True + right_pad_value: "10dp" + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif :align: center @@ -1524,6 +1770,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): and defaults to `False`. """ + right_pad_value = NumericProperty(0) + """ + See :attr:`~right_pad` parameter for more information. + + :attr:`right_pad_value` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0`. + """ + root_button_anim = BooleanProperty(False) """ If ``True`` then the root button will rotate 45 degrees when the stack @@ -1612,39 +1866,87 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): bg_color_root_button = ColorProperty(None) """ - Root button color in (r, g, b, a) format. + Background color of root button in (r, g, b, a) or string format. + + .. code-clock:: kv + + MDFloatingActionButtonSpeedDial: + bg_color_root_button: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-root-button.png + :align: center :attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. + and defaults to `None`. """ bg_color_stack_button = ColorProperty(None) """ - The color of the buttons in the stack (r, g, b, a) format. + Background color of the stack buttons in (r, g, b, a) or string format. + + .. code-clock:: kv + + MDFloatingActionButtonSpeedDial: + bg_color_root_button: "red" + bg_color_stack_button: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-stack-button.png + :align: center :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. + and defaults to `None`. """ color_icon_stack_button = ColorProperty(None) """ - The color icon of the buttons in the stack (r, g, b, a) format. + The color icon of the stack buttons in (r, g, b, a) or string format. + + .. code-clock:: kv + + MDFloatingActionButtonSpeedDial: + bg_color_root_button: "red" + bg_color_stack_button: "red" + color_icon_stack_button: "white" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-stack-button.png + :align: center :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. + and defaults to `None`. """ color_icon_root_button = ColorProperty(None) """ - The color icon of the root button (r, g, b, a) format. + The color icon of the root button in (r, g, b, a) or string format. + + .. code-clock:: kv + + MDFloatingActionButtonSpeedDial: + bg_color_root_button: "red" + bg_color_stack_button: "red" + color_icon_stack_button: "white" + color_icon_root_button: self.color_icon_stack_button + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-root-button.png + :align: center :attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. + and defaults to `None`. """ bg_hint_color = ColorProperty(None) """ - Background color for the text of the buttons in the stack (r, g, b, a) format. + Background color for the floating text of the buttons in (r, g, b, a) + or string format. + + .. code-clock:: kv + + MDFloatingActionButtonSpeedDial: + bg_hint_color: "red" + hint_animation: True + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-hint-color.png + :align: center :attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -1652,12 +1954,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): hint_animation = BooleanProperty(False) """ - Whether to use button extension animation to display text labels. + Whether to use button extension animation to display floating text. :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty` and defaults to `False`. """ + stack_buttons = DictProperty() + _label_pos_y_set = False _anim_buttons_data = {} _anim_labels_data = {} @@ -1666,6 +1970,8 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): super().__init__(**kwargs) self.register_event_type("on_open") self.register_event_type("on_close") + self.register_event_type("on_press_stack_button") + self.register_event_type("on_release_stack_button") Window.bind(on_resize=self._update_pos_buttons) def on_open(self, *args): @@ -1700,19 +2006,22 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): if self.state == "open": for widget in self.children: if isinstance(widget, MDFloatingLabel) and self.hint_animation: - widget._elevation = 0 Animation.cancel_all(widget) for item in self.data.items(): if widget.text in item: Animation( _canvas_width=widget.width + dp(24), - _padding_right=dp(5) if self.right_pad else 0, + _padding_right=self.right_pad_value + if self.right_pad + else 0, d=self.opening_time, t=self.opening_transition, ).start(instance_button) if ( instance_button.icon == self.data[f"{widget.text}"] + or instance_button.icon + == self.data[f"{widget.text}"][0] ): Animation( opacity=1, @@ -1727,51 +2036,68 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): def on_data(self, instance_speed_dial, data: dict) -> None: """Creates a stack of buttons.""" - # FIXME: Don't know how to fix AttributeError error: - # File "kivymd/uix/button.py", line 1597, in on_data - # self.add_widget(bottom_button) - # File "kivy/uix/floatlayout.py", line 140, in add_widget - # return super(FloatLayout, self).add_widget(widget, index, canvas) - # File "kivy/uix/layout.py", line 97, in add_widget - # return super(Layout, self).add_widget(widget, index, canvas) - # File "kivy/uix/widget.py", line 629, in add_widget - # canvas.add(widget.canvas) - # AttributeError: 'NoneType' object has no attribute 'add' - super().__init__() + def on_data(*args): + # Bottom buttons. + for name, parameters in data.items(): + name_icon = ( + parameters if (type(parameters) is str) else parameters[0] + ) + + bottom_button = MDFloatingBottomButton( + icon=name_icon, + on_enter=self.on_enter, + on_leave=self.on_leave, + opacity=0, + ) + bottom_button.bind( + on_press=lambda x: self.dispatch("on_press_stack_button"), + on_release=lambda x: self.dispatch( + "on_release_stack_button" + ), + ) + + if "on_press" in parameters: + callback = parameters[parameters.index("on_press") + 1] + bottom_button.bind(on_press=callback) + + if "on_release" in parameters: + callback = parameters[parameters.index("on_release") + 1] + bottom_button.bind(on_release=callback) + + self.set_pos_bottom_buttons(bottom_button) + self.add_widget(bottom_button) + self.stack_buttons[name] = WeakProxy(bottom_button) + # Labels. + floating_text = name + if floating_text: + label = MDFloatingLabel(text=floating_text, opacity=0) + label.bg_color = self.label_bg_color + label.radius = self.label_radius + label.text_color = ( + self.label_text_color + if self.label_text_color + else self.theme_cls.text_color + ) + self.add_widget(label) + # Top root button. + root_button = MDFloatingRootButton(on_release=self.open_stack) + root_button.icon = self.icon + self.set_pos_root_button(root_button) + self.add_widget(root_button) + self.clear_widgets() + self.stack_buttons = {} self._anim_buttons_data = {} self._anim_labels_data = {} self._label_pos_y_set = False - - # Bottom buttons. - for name, name_icon in data.items(): - bottom_button = MDFloatingBottomButton( - icon=name_icon, - on_enter=self.on_enter, - on_leave=self.on_leave, - opacity=0, - ) - bottom_button.bind( - on_release=lambda x=bottom_button: self.callback(x) - ) - self.set_pos_bottom_buttons(bottom_button) - self.add_widget(bottom_button) - # Labels. - floating_text = name - if floating_text: - label = MDFloatingLabel(text=floating_text, opacity=0) - label.text_color = self.label_text_color - self.add_widget(label) - # Top root button. - root_button = MDFloatingRootButton(on_release=self.open_stack) - root_button.icon = self.icon - self.set_pos_root_button(root_button) - self.add_widget(root_button) + Clock.schedule_once(on_data) def on_icon(self, instance_speed_dial, name_icon: str) -> None: - self._get_count_widget(MDFloatingRootButton).icon = name_icon + self._set_button_property(MDFloatingRootButton, "icon", name_icon) - def on_label_text_color(self, instance_speed_dial, color: list) -> None: + def on_label_text_color( + self, instance_speed_dial, color: list | str + ) -> None: for widget in self.children: if isinstance(widget, MDFloatingLabel): widget.text_color = color @@ -1779,34 +2105,52 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): def on_color_icon_stack_button( self, instance_speed_dial, color: list ) -> None: - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget.text_color = color + self._set_button_property(MDFloatingBottomButton, "icon_color", color) def on_hint_animation(self, instance_speed_dial, value: bool) -> None: for widget in self.children: if isinstance(widget, MDFloatingLabel): - widget.bg_color = (0, 0, 0, 0) + widget.md_bg_color = (0, 0, 0, 0) def on_bg_hint_color(self, instance_speed_dial, color: list) -> None: - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget._bg_color = color + setattr(MDFloatingBottomButton, "_bg_color", color) def on_color_icon_root_button( self, instance_speed_dial, color: list ) -> None: - self._get_count_widget(MDFloatingRootButton).text_color = color + self._set_button_property(MDFloatingRootButton, "icon_color", color) def on_bg_color_stack_button( self, instance_speed_dial, color: list ) -> None: - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget.md_bg_color = color + self._set_button_property(MDFloatingBottomButton, "md_bg_color", color) def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None: - self._get_count_widget(MDFloatingRootButton).md_bg_color = color + self._set_button_property(MDFloatingRootButton, "md_bg_color", color) + + def on_press_stack_button(self, *args) -> None: + """ + Called at the on_press event for the stack button. + + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + on_press_stack_button: print(*args) + + .. versionadded:: 1.1.0 + """ + + def on_release_stack_button(self, *args) -> None: + """ + Called at the on_release event for the stack button. + + .. code-block:: kv + + MDFloatingActionButtonSpeedDial: + on_release_stack_button: print(*args) + + .. versionadded:: 1.1.0 + """ def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None: """ @@ -1860,7 +2204,7 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): if self.state != "open": y = 0 - label_position = dp(56) + label_position = dp(54) anim_buttons_data = {} anim_labels_data = {} @@ -1892,7 +2236,7 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): ): # Rotates the root button 45 degrees. Animation( - _angle=-45, + rotate_value_angle=-45, d=self.opening_time_button_rotation, t=self.opening_transition_button_rotation, ).start(widget) @@ -1951,13 +2295,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): opacity=0, ).start(widget) elif isinstance(widget, MDFloatingLabel): - Animation(opacity=0, d=0.1).start(widget) + if widget.opacity > 0: + Animation(opacity=0, d=0.1).start(widget) elif ( isinstance(widget, MDFloatingRootButton) and self.root_button_anim ): Animation( - _angle=0, + rotate_value_angle=0, d=self.closing_time_button_rotation, t=self.closing_transition_button_rotation, ).start(widget) @@ -1974,9 +2319,15 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): elif isinstance(widget, MDFloatingLabel): self.set_pos_labels(widget) - def _get_count_widget(self, instance): - widget = None - for widget in self.children: - if isinstance(widget, instance): - break - return widget + def _set_button_property( + self, instance, property_name: str, property_value: str | list + ): + def set_count_widget(*args): + if self.children: + for widget in self.children: + if isinstance(widget, instance): + setattr(instance, property_name, property_value) + Clock.unschedule(set_count_widget) + break + + Clock.schedule_interval(set_count_widget, 0) diff --git a/sbapp/kivymd/uix/card/card.kv b/sbapp/kivymd/uix/card/card.kv index ac67a51..7ed37bd 100644 --- a/sbapp/kivymd/uix/card/card.kv +++ b/sbapp/kivymd/uix/card/card.kv @@ -2,17 +2,6 @@ md_bg_color: app.theme_cls.divider_color - - canvas.before: - Color: - rgba: self.md_bg_color - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.radius - source: root.background - - md_bg_color: self.theme_cls.divider_color \ diff --git a/sbapp/kivymd/uix/card/card.py b/sbapp/kivymd/uix/card/card.py index 85f7f27..d224ae1 100755 --- a/sbapp/kivymd/uix/card/card.py +++ b/sbapp/kivymd/uix/card/card.py @@ -26,97 +26,141 @@ Components/Card MDCard ------ -.. warning:: Starting from the KivyMD 1.0.0 library version, it is necessary - to manually inherit the card class from one of the ``Elevation`` classes - from ``kivymd/uix/behaviors/elevation.py`` module to draw the card shadow. - -.. code-block:: python - - from kivymd.uix.behaviors import RoundedRectangularElevationBehavior - from kivymd.uix.card import MDCard - - - class MD3Card(MDCard, RoundedRectangularElevationBehavior): - '''Implements a material design v3 card.''' - -This may sound awkward to you, but it actually allows for better control over -the providers that implement the rendering of the shadows. - -.. note:: You can read more information about the classes that implement the - rendering of shadows on this `documentation page `_. - An example of the implementation of a card in the style of material design version 3 ------------------------------------------------------------------------------------ -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import StringProperty + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.behaviors import RoundedRectangularElevationBehavior - from kivymd.uix.card import MDCard + .. code-block:: python - KV = ''' - - padding: 16 - size_hint: None, None - size: "200dp", "100dp" + from kivy.lang import Builder + from kivy.properties import StringProperty - MDRelativeLayout: - size_hint: None, None - size: root.size + from kivymd.app import MDApp + from kivymd.uix.card import MDCard - MDIconButton: - icon: "dots-vertical" - pos: - root.width - (self.width + root.padding[0] + dp(4)), \ - root.height - (self.height + root.padding[0] + dp(4)) + KV = ''' + + padding: 4 + size_hint: None, None + size: "200dp", "100dp" - MDLabel: - id: label - text: root.text - adaptive_size: True - color: .2, .2, .2, .8 + MDRelativeLayout: + + MDIconButton: + icon: "dots-vertical" + pos_hint: {"top": 1, "right": 1} + + MDLabel: + id: label + text: root.text + adaptive_size: True + color: "grey" + pos: "12dp", "12dp" + bold: True - MDScreen: + MDScreen: - MDBoxLayout: - id: box - adaptive_size: True - spacing: "56dp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' + MDBoxLayout: + id: box + adaptive_size: True + spacing: "56dp" + pos_hint: {"center_x": .5, "center_y": .5} + ''' - class MD3Card(MDCard, RoundedRectangularElevationBehavior): - '''Implements a material design v3 card.''' + class MD3Card(MDCard): + '''Implements a material design v3 card.''' - text = StringProperty() + text = StringProperty() - class TestCard(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + return Builder.load_string(KV) - def on_start(self): - styles = { - "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4" - } - for style in styles.keys(): - self.root.ids.box.add_widget( - MD3Card( - line_color=(0.2, 0.2, 0.2, 0.8), - style=style, - text=style.capitalize(), - md_bg_color=styles[style], + def on_start(self): + styles = { + "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4" + } + for style in styles.keys(): + self.root.ids.box.add_widget( + MD3Card( + line_color=(0.2, 0.2, 0.2, 0.8), + style=style, + text=style.capitalize(), + md_bg_color=styles[style], + ) + ) + + + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.card import MDCard + from kivymd.uix.label import MDLabel + from kivymd.uix.relativelayout import MDRelativeLayout + from kivymd.uix.screen import MDScreen + + + class MD3Card(MDCard): + '''Implements a material design v3 card.''' + + + class Example(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + return ( + MDScreen( + MDBoxLayout( + id="box", + adaptive_size=True, + spacing="56dp", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) ) - ) + + def on_start(self): + styles = { + "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4" + } + for style in styles.keys(): + self.root.ids.box.add_widget( + MD3Card( + MDRelativeLayout( + MDIconButton( + icon="dots-vertical", + pos_hint={"top": 1, "right": 1} + ), + MDLabel( + text=style.capitalize(), + adaptive_size=True, + color="grey", + pos=("12dp", "12dp"), + ), + ), + line_color=(0.2, 0.2, 0.2, 0.8), + style=style, + padding="4dp", + size_hint=(None, None), + size=("200dp", "100dp"), + md_bg_color=styles[style], + ) + ) - TestCard().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards-m3.png :align: center @@ -125,16 +169,13 @@ An example of the implementation of a card in the style of material design versi MDCardSwipe ----------- -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDCardSwipe.gif - :align: center - To create a card with `swipe-to-delete` behavior, you must create a new class that inherits from the :class:`~MDCardSwipe` class: .. code-block:: kv - : + size_hint_y: None height: content.height @@ -152,80 +193,142 @@ that inherits from the :class:`~MDCardSwipe` class: class SwipeToDeleteItem(MDCardSwipe): text = StringProperty() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/map-mdcard-swipr.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sceleton-mdcard-swiper.png :align: center End full code ------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import StringProperty + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe + .. code-block:: python - KV = ''' - : - size_hint_y: None - height: content.height + from kivy.lang import Builder + from kivy.properties import StringProperty - MDCardSwipeLayerBox: - # Content under the card. + from kivymd.app import MDApp + from kivymd.uix.card import MDCardSwipe - MDCardSwipeFrontBox: + KV = ''' + + size_hint_y: None + height: content.height - # Content of card. - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True + MDCardSwipeLayerBox: + # Content under the card. + + MDCardSwipeFrontBox: + + # Content of card. + OneLineListItem: + id: content + text: root.text + _no_ripple_effect: True - MDScreen: + MDScreen: - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" + MDBoxLayout: + orientation: "vertical" - MDTopAppBar: - elevation: 10 - title: "MDCardSwipe" + MDTopAppBar: + elevation: 4 + title: "MDCardSwipe" - ScrollView: - scroll_timeout : 100 + MDScrollView: + scroll_timeout : 100 - MDList: - id: md_list - padding: 0 - ''' + MDList: + id: md_list + padding: 0 + ''' - class SwipeToDeleteItem(MDCardSwipe): - '''Card with `swipe-to-delete` behavior.''' + class SwipeToDeleteItem(MDCardSwipe): + '''Card with `swipe-to-delete` behavior.''' - text = StringProperty() + text = StringProperty() - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def build(self): - return self.screen + def on_start(self): + '''Creates a list of cards.''' - def on_start(self): - '''Creates a list of cards.''' - - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) + for i in range(20): + self.root.ids.md_list.add_widget( + SwipeToDeleteItem(text=f"One-line item {i}") + ) - TestCard().run() + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.card import ( + MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox + ) + from kivymd.uix.list import MDList, OneLineListItem + from kivymd.uix.screen import MDScreen + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.toolbar import MDTopAppBar + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDBoxLayout( + MDTopAppBar( + elevation=4, + title="MDCardSwipe", + ), + MDScrollView( + MDList( + id="md_list", + ), + id="scroll", + scroll_timeout=100, + ), + id="box", + orientation="vertical", + ), + ) + ) + + def on_start(self): + '''Creates a list of cards.''' + + for i in range(20): + self.root.ids.box.ids.scroll.ids.md_list.add_widget( + MDCardSwipe( + MDCardSwipeLayerBox(), + MDCardSwipeFrontBox( + OneLineListItem( + id="content", + text=f"One-line item {i}", + _no_ripple_effect=True, + ) + ), + size_hint_y=None, + height="52dp", + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-mdcard-swipe.gif :align: center @@ -235,7 +338,7 @@ Binding a swipe to one of the sides of the screen .. code-block:: kv - : + # By default, the parameter is "left" anchor: "right" @@ -250,7 +353,7 @@ Swipe behavior .. code-block:: kv - : + # By default, the parameter is "hand" type_swipe: "hand" @@ -271,85 +374,41 @@ Removing an item using the ``type_swipe = "auto"`` parameter The map provides the :attr:`MDCardSwipe.on_swipe_complete` event. You can use this event to remove items from a list: -.. code-block:: kv +.. tabs:: - : - on_swipe_complete: app.on_swipe_complete(root) + .. tab:: Declarative KV styles -.. code-block:: python + .. code-block:: kv - def on_swipe_complete(self, instance): - self.screen.ids.md_list.remove_widget(instance) + : + on_swipe_complete: app.on_swipe_complete(root) -End full code -------------- + .. tab:: Declarative python styles -.. code-block:: python + .. code-block:: kv - from kivy.lang import Builder - from kivy.properties import StringProperty + .. code-block:: python - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe - - KV = ''' - : - size_hint_y: None - height: content.height - type_swipe: "auto" - on_swipe_complete: app.on_swipe_complete(root) - - MDCardSwipeLayerBox: - - MDCardSwipeFrontBox: - - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" - - MDTopAppBar: - elevation: 10 - title: "MDCardSwipe" - - ScrollView: - - MDList: - id: md_list - padding: 0 - ''' - - - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() - - - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - def on_swipe_complete(self, instance): - self.screen.ids.md_list.remove_widget(instance) - - def on_start(self): - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") + MDCardSwipe( + ... + on_swipe_complete=self.on_swipe_complete, ) +.. tabs:: - TestCard().run() + .. tab:: Imperative python styles + + .. code-block:: python + + def on_swipe_complete(self, instance): + self.root.ids.md_list.remove_widget(instance) + + .. tab:: Decralative python styles + + .. code-block:: python + + def on_swipe_complete(self, instance): + self.root.ids.box.ids.scroll.ids.md_list.remove_widget(instance) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif :align: center @@ -375,76 +434,154 @@ use the :class:`~MDCardSwipeLayerBox` class. End full code ------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import StringProperty + .. tab:: Declarative KV styles - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe + .. code-block:: python - KV = ''' - : - size_hint_y: None - height: content.height + from kivy.lang import Builder + from kivy.properties import StringProperty - MDCardSwipeLayerBox: - padding: "8dp" + from kivymd.app import MDApp + from kivymd.uix.card import MDCardSwipe - MDIconButton: - icon: "trash-can" - pos_hint: {"center_y": .5} - on_release: app.remove_item(root) + KV = ''' + : + size_hint_y: None + height: content.height - MDCardSwipeFrontBox: + MDCardSwipeLayerBox: + padding: "8dp" - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True + MDIconButton: + icon: "trash-can" + pos_hint: {"center_y": .5} + on_release: app.remove_item(root) + + MDCardSwipeFrontBox: + + OneLineListItem: + id: content + text: root.text + _no_ripple_effect: True - MDScreen: + MDScreen: - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" + MDBoxLayout: + orientation: "vertical" - MDTopAppBar: - elevation: 10 - title: "MDCardSwipe" + MDTopAppBar: + elevation: 4 + title: "MDCardSwipe" - ScrollView: + MDScrollView: - MDList: - id: md_list - padding: 0 - ''' + MDList: + id: md_list + padding: 0 + ''' - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() + class SwipeToDeleteItem(MDCardSwipe): + text = StringProperty() - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) + class Example(MDApp): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + self.screen = Builder.load_string(KV) - def build(self): - return self.screen + def build(self): + return self.screen - def remove_item(self, instance): - self.screen.ids.md_list.remove_widget(instance) + def remove_item(self, instance): + self.screen.ids.md_list.remove_widget(instance) - def on_start(self): - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) + def on_start(self): + for i in range(20): + self.screen.ids.md_list.add_widget( + SwipeToDeleteItem(text=f"One-line item {i}") + ) - TestCard().run() + Example().run() + + .. tab:: Decralative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.card import ( + MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox + ) + from kivymd.uix.list import MDList, OneLineListItem + from kivymd.uix.screen import MDScreen + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.toolbar import MDTopAppBar + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDBoxLayout( + MDTopAppBar( + elevation=4, + title="MDCardSwipe", + ), + MDScrollView( + MDList( + id="md_list", + ), + id="scroll", + scroll_timeout=100, + ), + id="box", + orientation="vertical", + ), + ) + ) + + def on_start(self): + '''Creates a list of cards.''' + + for i in range(20): + self.root.ids.box.ids.scroll.ids.md_list.add_widget( + MDCardSwipe( + MDCardSwipeLayerBox( + MDIconButton( + icon="trash-can", + pos_hint={"center_y": 0.5}, + on_release=self.remove_item, + ), + ), + MDCardSwipeFrontBox( + OneLineListItem( + id="content", + text=f"One-line item {i}", + _no_ripple_effect=True, + ) + ), + size_hint_y=None, + height="52dp", + ) + ) + + def remove_item(self, instance): + self.root.ids.box.ids.scroll.ids.md_list.remove_widget( + instance.parent.parent + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif :align: center @@ -457,6 +594,67 @@ Focus behavior MDCard: focus_behavior: True +.. tabs:: + + .. tab:: Declarative KV styles + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDCard: + size_hint: .7, .4 + focus_behavior: True + pos_hint: {"center_x": .5, "center_y": .5} + md_bg_color: "darkgrey" + unfocus_color: "darkgrey" + focus_color: "grey" + elevation: 6 + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.card import MDCard + from kivymd.uix.screen import MDScreen + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + MDScreen( + MDCard( + size_hint=(0.7, 0.4), + focus_behavior=True, + pos_hint={"center_x": 0.5, "center_y": 0.5}, + md_bg_color="darkgrey", + unfocus_color="darkgrey", + focus_color="grey", + elevation=6, + ), + ) + ) + + + Example().run() + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif :align: center @@ -471,88 +669,6 @@ Ripple behavior .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif :align: center -End full code -------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - - icon: "star" - on_release: self.icon = "star-outline" if self.icon == "star" else "star" - - - MDScreen: - - MDCard: - orientation: "vertical" - size_hint: .5, None - height: box_top.height + box_bottom.height - focus_behavior: True - ripple_behavior: True - pos_hint: {"center_x": .5, "center_y": .5} - - MDBoxLayout: - id: box_top - spacing: "20dp" - adaptive_height: True - - FitImage: - source: "/Users/macbookair/album.jpeg" - size_hint: .3, None - height: text_box.height - - MDBoxLayout: - id: text_box - orientation: "vertical" - adaptive_height: True - spacing: "10dp" - padding: 0, "10dp", "10dp", "10dp" - - MDLabel: - text: "Ride the Lightning" - theme_text_color: "Primary" - font_style: "H5" - bold: True - adaptive_height: True - - MDLabel: - text: "July 27, 1984" - adaptive_height: True - theme_text_color: "Primary" - - MDSeparator: - - MDBoxLayout: - id: box_bottom - adaptive_height: True - padding: "10dp", 0, 0, 0 - - MDLabel: - text: "Rate this album" - adaptive_height: True - pos_hint: {"center_y": .5} - theme_text_color: "Primary" - - StarButton: - StarButton: - StarButton: - StarButton: - StarButton: - ''' - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Test().run() """ __all__ = ( @@ -579,17 +695,20 @@ from kivy.properties import ( VariableListProperty, ) from kivy.uix.boxlayout import BoxLayout -from kivy.uix.relativelayout import RelativeLayout +from kivy.utils import get_color_from_hex from kivymd import uix_path from kivymd.color_definitions import colors from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( BackgroundColorBehavior, - FocusBehavior, + CommonElevationBehavior, + DeclarativeBehavior, RectangularRippleBehavior, ) +from kivymd.uix.behaviors.focus_behavior import FocusBehavior from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.relativelayout import MDRelativeLayout with open( os.path.join(uix_path, "card", "card.kv"), encoding="utf-8" @@ -602,7 +721,7 @@ class MDSeparator(ThemableBehavior, MDBoxLayout): color = ColorProperty(None) """ - Separator color in ``rgba`` format. + Separator color. :attr:`color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -623,9 +742,11 @@ class MDSeparator(ThemableBehavior, MDBoxLayout): class MDCard( + DeclarativeBehavior, ThemableBehavior, BackgroundColorBehavior, RectangularRippleBehavior, + CommonElevationBehavior, FocusBehavior, BoxLayout, ): @@ -645,14 +766,6 @@ class MDCard( and defaults to `False`. """ - elevation = NumericProperty(None, allownone=True) - """ - Elevation value. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to 1. - """ - radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)]) """ Card radius by default. @@ -676,15 +789,16 @@ class MDCard( """ _bg_color_map = ( - colors["Light"]["CardsDialogs"], - colors["Dark"]["CardsDialogs"], + get_color_from_hex(colors["Light"]["CardsDialogs"]), + get_color_from_hex(colors["Dark"]["CardsDialogs"]), [1.0, 1.0, 1.0, 0.0], ) - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind(theme_style=self.update_md_bg_color) - self.theme_cls.bind(material_style=self.set_style) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.theme_cls.bind( + material_style=self.set_style, theme_style=self.update_md_bg_color + ) Clock.schedule_once(self.set_style) Clock.schedule_once( lambda x: self.on_ripple_behavior(0, self.ripple_behavior) @@ -693,24 +807,26 @@ class MDCard( def update_md_bg_color(self, instance_card, theme_style: str) -> None: if self.md_bg_color in self._bg_color_map: - self.md_bg_color = colors[theme_style]["CardsDialogs"] + self.md_bg_color = get_color_from_hex( + colors[theme_style]["CardsDialogs"] + ) def set_style(self, *args) -> None: self.set_radius() self.set_elevation() self.set_line_color() - def set_line_color(self): + def set_line_color(self) -> None: if self.theme_cls.material_style == "M3": if self.style == "elevated" or self.style == "filled": self.line_color = [0, 0, 0, 0] - def set_elevation(self): + def set_elevation(self) -> None: if self.theme_cls.material_style == "M3": if self.style == "outlined" or self.style == "filled": self.elevation = 0 elif self.style == "elevated": - self.elevation = 1 + self.elevation = 2 def set_radius(self) -> None: if ( @@ -730,7 +846,7 @@ class MDCard( self._no_ripple_effect = False if value_behavior else True -class MDCardSwipe(RelativeLayout): +class MDCardSwipe(MDRelativeLayout): """ :Events: :attr:`on_swipe_complete` @@ -765,6 +881,16 @@ class MDCardSwipe(RelativeLayout): and defaults to `'out_sine'`. """ + closing_interval = NumericProperty(0) + """ + Interval for closing the front layer. + + .. versionadded:: 1.1.0 + + :attr:`closing_interval` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0`. + """ + anchor = OptionProperty("left", options=("left", "right")) """ Anchoring screen edge for card. Available options are: `'left'`, `'right'`. @@ -830,10 +956,11 @@ class MDCardSwipe(RelativeLayout): _opens_process = False _to_closed = True + _distance = 0 - def __init__(self, **kw): + def __init__(self, *args, **kwargs): self.register_event_type("on_swipe_complete") - super().__init__(**kw) + super().__init__(*args, **kwargs) def add_widget(self, widget, index=0, canvas=None): if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)): @@ -853,18 +980,24 @@ class MDCardSwipe(RelativeLayout): def on_open_progress( self, instance_swipe_to_delete_item, progress_value: float ) -> None: - if self.anchor == "left": - self.children[0].x = self.width * progress_value - else: - self.children[0].x = self.width * progress_value - self.width + def on_open_progress(*args): + if self.anchor == "left": + self.children[0].x = self.width * progress_value + else: + self.children[0].x = self.width * progress_value - self.width + + Clock.schedule_once(on_open_progress) def on_touch_move(self, touch): if self.collide_point(touch.x, touch.y): - expr = ( - touch.x < self.swipe_distance - if self.anchor == "left" - else touch.x > self.width - self.swipe_distance - ) + self._distance += touch.dx + expr = False + + if self.anchor == "left" and touch.dx >= 0: + expr = abs(self._distance) < self.swipe_distance + elif self.anchor == "right" and touch.dx < 0: + expr = abs(self._distance) > self.swipe_distance + if expr and not self._opens_process: self._opens_process = True self._to_closed = False @@ -875,6 +1008,7 @@ class MDCardSwipe(RelativeLayout): return super().on_touch_move(touch) def on_touch_up(self, touch): + self._distance = 0 if self.collide_point(touch.x, touch.y): if not self._to_closed: self._opens_process = False @@ -885,7 +1019,7 @@ class MDCardSwipe(RelativeLayout): if self.collide_point(touch.x, touch.y): if self.state == "opened": self._to_closed = True - self.close_card() + Clock.schedule_once(self.close_card, self.closing_interval) return super().on_touch_down(touch) def complete_swipe(self) -> None: @@ -895,7 +1029,7 @@ class MDCardSwipe(RelativeLayout): else self.open_progress >= self.max_swipe_x ) if expr: - self.close_card() + Clock.schedule_once(self.close_card, self.closing_interval) else: self.open_card() @@ -915,7 +1049,7 @@ class MDCardSwipe(RelativeLayout): anim.start(self.children[0]) self.state = "opened" - def close_card(self) -> None: + def close_card(self, *args) -> None: anim = Animation(x=0, t=self.closing_transition, d=self.opening_time) anim.bind(on_complete=self._reset_open_progress) anim.start(self.children[0]) diff --git a/sbapp/kivymd/uix/carousel.py b/sbapp/kivymd/uix/carousel.py index 600df2b..4b56dba 100644 --- a/sbapp/kivymd/uix/carousel.py +++ b/sbapp/kivymd/uix/carousel.py @@ -57,8 +57,10 @@ MDCarousel from kivy.animation import Animation from kivy.uix.carousel import Carousel +from kivymd.uix.behaviors import DeclarativeBehavior -class MDCarousel(Carousel): + +class MDCarousel(DeclarativeBehavior, Carousel): """ based on kivy's carousel. @@ -68,8 +70,8 @@ class MDCarousel(Carousel): _scrolling = False - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_slide_progress") self.register_event_type("on_slide_complete") diff --git a/sbapp/kivymd/uix/chip/__init__.py b/sbapp/kivymd/uix/chip/__init__.py index 86420ec..ec6050b 100644 --- a/sbapp/kivymd/uix/chip/__init__.py +++ b/sbapp/kivymd/uix/chip/__init__.py @@ -1 +1 @@ -from .chip import MDChip, MDChooseChip # NOQA F401 +from .chip import MDChip # NOQA F401 diff --git a/sbapp/kivymd/uix/chip/chip.py b/sbapp/kivymd/uix/chip/chip.py index 54f0b77..03e1a98 100755 --- a/sbapp/kivymd/uix/chip/chip.py +++ b/sbapp/kivymd/uix/chip/chip.py @@ -132,7 +132,7 @@ Use with elevation icon_right: "close-circle-outline" line_color: app.theme_cls.disabled_hint_text_color md_bg_color: 1, 0, 0, .5 - elevation: 12 + elevation: 4 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-with-elevation.png :align: center @@ -304,7 +304,6 @@ __all__ = ("MDChip",) import os -from kivy import Logger from kivy.animation import Animation from kivy.lang import Builder from kivy.metrics import dp @@ -314,14 +313,13 @@ from kivy.uix.behaviors import ButtonBehavior from kivymd import uix_path from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( - FakeRectangularElevationBehavior, + CommonElevationBehavior, RectangularRippleBehavior, + ScaleBehavior, TouchBehavior, ) from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.label import MDIcon -from kivymd.uix.stacklayout import MDStackLayout -from kivymd.uix.templates import ScaleWidget with open( os.path.join(uix_path, "chip", "chip.kv"), encoding="utf-8" @@ -330,12 +328,12 @@ with open( class MDChip( + MDBoxLayout, ThemableBehavior, RectangularRippleBehavior, - FakeRectangularElevationBehavior, - TouchBehavior, ButtonBehavior, - MDBoxLayout, + CommonElevationBehavior, + TouchBehavior, ): text = StringProperty() """ @@ -345,17 +343,6 @@ class MDChip( and defaults to `''`. """ - icon = StringProperty("checkbox-blank-circle", deprecated=True) - """ - Chip icon. - - .. deprecated:: 1.0.0 - Use :attr:`icon_right` and :attr:`icon_left` instead. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - icon_left = StringProperty() """ Chip left icon. @@ -376,16 +363,6 @@ class MDChip( and defaults to `''`. """ - color = ColorProperty(None, deprecated=True) - """ - Chip color in ``rgba`` format. - - .. deprecated:: 1.0.0 - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - text_color = ColorProperty(None) """ Chip's text color in ``rgba`` format. @@ -394,17 +371,6 @@ class MDChip( and defaults to `None`. """ - icon_color = ColorProperty(None, deprecated=True) - """ - Chip's icon color in ``rgba`` format. - - .. deprecated:: 1.0.0 - Use :attr:`icon_right_color` and :attr:`icon_left_color` instead. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - icon_right_color = ColorProperty(None) """ Chip's right icon color in ``rgba`` format. @@ -435,26 +401,6 @@ class MDChip( and defaults to `None`. """ - check = BooleanProperty(False, deprecated=True) - """ - If `True`, a checkmark is added to the left when touch to the chip. - - .. deprecated:: 1.0.0 - - :attr:`check` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - selected_chip_color = ColorProperty(None, deprecated=True) - """ - The color of the chip that is currently selected in ``rgba`` format. - - .. deprecated:: 1.0.0 - - :attr:`selected_chip_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - active = BooleanProperty(False) """ Whether the check is marked or not. @@ -508,16 +454,7 @@ class MDChip( self.active = False -class MDChooseChip(MDStackLayout): - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "MDChooseChip: " - "class is deprecated and will be removed in a future version" - ) - - -class MDScalableCheckIcon(MDIcon, ScaleWidget): +class MDScalableCheckIcon(MDIcon, ScaleBehavior): pos_hint = {"center_y": 0.5} diff --git a/sbapp/kivymd/uix/controllers/windowcontroller.py b/sbapp/kivymd/uix/controllers/windowcontroller.py index 9ace0c4..9a5dacd 100644 --- a/sbapp/kivymd/uix/controllers/windowcontroller.py +++ b/sbapp/kivymd/uix/controllers/windowcontroller.py @@ -2,6 +2,8 @@ Controllers/WindowController ============================ +.. versionadded:: 1.0.0 + Modules and classes that implement useful methods for getting information about the state of the current application window. @@ -33,16 +35,35 @@ Controlling the resizing direction of the application window from kivy.core.window import Window from kivy.core.window.window_sdl2 import WindowSDL +from kivy.metrics import dp class WindowController: def __init__(self): self.window_resizing_direction = "unknown" + self.real_device_type = "unknown" self.__width = Window.width Window.bind(on_resize=self._on_resize) + def on_size(self, instance, size: list) -> None: + """Called when the application screen size changes.""" + + window_width = size[0] + + if window_width < dp(500): + self.real_device_type = "mobile" + elif window_width < dp(1100): + self.real_device_type = "tablet" + else: + self.real_device_type = "desktop" + + def get_real_device_type(self) -> str: + """Returns the device type - 'mobile', 'tablet' or 'desktop'.""" + + return self.real_device_type + def get_window_width_resizing_direction(self) -> str: - """Return window width resizing direction - 'left' or 'right'""" + """Return window width resizing direction - 'left' or 'right'.""" return self.window_resizing_direction diff --git a/sbapp/kivymd/uix/datatables/datatables.kv b/sbapp/kivymd/uix/datatables/datatables.kv index 188ddde..a6a7220 100644 --- a/sbapp/kivymd/uix/datatables/datatables.kv +++ b/sbapp/kivymd/uix/datatables/datatables.kv @@ -1,5 +1,4 @@ #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE -#:import FakeRectangularElevationBehavior kivymd.uix.behaviors.FakeRectangularElevationBehavior @@ -66,7 +65,7 @@ size_hint_y: None height: self.minimum_height spacing: "4dp" - tooltip_text: root.text + tooltip_text: root.tooltip if root.tooltip else root.text BoxLayout: id: box @@ -175,7 +174,11 @@ font_size: "14sp" on_release: root.table_data.open_pagination_menu() text: - f"{root.table_data.rows_num if root.table_data.rows_num < len(root.table_data.row_data) else len(root.table_data.row_data)}" + "{}".format( \ + root.table_data.rows_num \ + if root.table_data.rows_num < len(root.table_data.row_data) else \ + len(root.table_data.row_data) \ + ) Widget: size_hint_x: None @@ -192,9 +195,11 @@ if root.theme_cls.theme_style == "Dark" else \ (0, 0, 0, 1) text: - f"1-" \ - f"{root.table_data.rows_num if root.table_data.rows_num > len(root.table_data.row_data) else len(root.table_data.row_data)} " \ - f"of {len(root.table_data.row_data)}" + "1-{} of {}".format( \ + root.table_data.rows_num \ + if root.table_data.rows_num > len(root.table_data.row_data) else \ + len(root.table_data.row_data), len(root.table_data.row_data) \ + ) MDIconButton: id: button_back @@ -217,7 +222,7 @@ on_release: root.table_data.set_next_row_data_parts("forward") - + diff --git a/sbapp/kivymd/uix/datatables/datatables.py b/sbapp/kivymd/uix/datatables/datatables.py index b574d06..1626283 100644 --- a/sbapp/kivymd/uix/datatables/datatables.py +++ b/sbapp/kivymd/uix/datatables/datatables.py @@ -11,19 +11,6 @@ Components/DataTables .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png :align: center -Warnings ---------- - -.. warning:: Data tables are still far from perfect. The class is in constant - change, because of optimizations and bug fixes. If you find a bug or have - an improvement you want to share, take some time and share your discoveries - with us over the main git repo. - Any help is well appreciated. - -.. warning:: In versions prior to `Kivy 2.1.0-dev0` exists an error in which is - the table has only one row in the current page, the table will only render - one column instead of the whole row. - .. note:: `MDDataTable` allows developers to sort the data provided by column. This happens thanks to the use of an external function that you can bind while you're defining the table columns. Be aware that the sorting function @@ -159,6 +146,15 @@ class CellHeader(MDTooltip, BoxLayout): and defaults to `''`. """ + tooltip = StringProperty() + """ + Tooltip containing descriptive text for the column. + If the tooltip is not provided, column `text` shall be used instead. + + :attr:`tooltip` is a :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + # TODO: Added example. sort_action = ObjectProperty() """ @@ -340,11 +336,19 @@ class TableHeader(ThemableBehavior, ScrollView): CellHeader( text=col_heading[0], sort_action=col_heading[2], + tooltip=col_heading[3], width=self.cols_minimum[i], table_data=self.table_data, is_sorted=(col_heading[0] == self.sorted_on), sorted_order=self.sorted_order, ) + if len(col_heading) == 4 + else CellHeader( + text=col_heading[0], + sort_action=col_heading[2], + width=self.cols_minimum[i], + table_data=self.table_data, + ) if len(col_heading) == 3 else CellHeader( text=col_heading[0], @@ -356,6 +360,9 @@ class TableHeader(ThemableBehavior, ScrollView): else: # Sets the text in the first cell. self.ids.first_cell.text = col_heading[0] + self.ids.first_cell.tooltip = ( + col_heading[3] if len(col_heading) == 4 else "" + ) self.ids.first_cell.ids.separator.height = 0 self.ids.first_cell.width = self.cols_minimum[i] @@ -765,6 +772,9 @@ class TablePagination(ThemableBehavior, MDBoxLayout): class MDDataTable(ThemableBehavior, AnchorLayout): """ + See :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation for + more information. + :Events: :attr:`on_row_press` Called when a table row is clicked. @@ -775,7 +785,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout): .. code-block:: python - from kivy.metrics import dp from kivymd.app import MDApp @@ -914,38 +923,82 @@ class MDDataTable(ThemableBehavior, AnchorLayout): """ Data for header columns. - .. code-block:: python + .. tabs:: - from kivy.metrics import dp + .. tab:: Imperative python style - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivy.uix.anchorlayout import AnchorLayout + .. code-block:: python + + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.uix.datatables import MDDataTable + from kivy.uix.anchorlayout import AnchorLayout - class Example(MDApp): - def build(self): - layout = AnchorLayout() - self.data_tables = MDDataTable( - size_hint=(0.7, 0.6), - use_pagination=True, - check=True, - # name column, width column, sorting function column(optional) - column_data=[ - ("No.", dp(30)), - ("Status", dp(30)), - ("Signal Name", dp(60)), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")), - ("Team Lead", dp(30)), - ], - ) - layout.add_widget(self.data_tables) - return layout + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + + layout = AnchorLayout() + self.data_tables = MDDataTable( + size_hint=(0.7, 0.6), + use_pagination=True, + check=True, + # name column, width column, sorting function column(optional), custom tooltip + column_data=[ + ("No.", dp(30), None, "Custom tooltip"), + ("Status", dp(30)), + ("Signal Name", dp(60)), + ("Severity", dp(30)), + ("Stage", dp(30)), + ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")), + ("Team Lead", dp(30)), + ], + ) + layout.add_widget(self.data_tables) + return layout - Example().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.uix.anchorlayout import MDAnchorLayout + from kivymd.uix.datatables import MDDataTable + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return MDAnchorLayout( + MDDataTable( + size_hint=(0.7, 0.6), + use_pagination=True, + check=True, + # name column, width column, sorting function column(optional) + column_data=[ + ("No.", dp(30)), + ("Status", dp(30)), + ("Signal Name", dp(60)), + ("Severity", dp(30)), + ("Stage", dp(30)), + ("Schedule", dp(30), + lambda *args: print("Sorted using Schedule")), + ("Team Lead", dp(30)), + ], + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png :align: center @@ -1060,6 +1113,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout): class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + layout = AnchorLayout() data_tables = MDDataTable( size_hint=(0.9, 0.6), @@ -1187,7 +1243,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout): """ Use or not use checkboxes for rows. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.png :align: center :attr:`check` is an :class:`~kivy.properties.BooleanProperty` @@ -1209,6 +1265,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout): class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + layout = AnchorLayout() data_tables = MDDataTable( size_hint=(0.9, 0.6), @@ -1238,19 +1297,19 @@ class MDDataTable(ThemableBehavior, AnchorLayout): and defaults to `False`. """ - elevation = NumericProperty(8) + elevation = NumericProperty(4) """ Table elevation. :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `8`. + and defaults to `4`. """ rows_num = NumericProperty(5) """ The number of rows displayed on one page of the table. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination-rows-num.png :align: center :attr:`rows_num` is an :class:`~kivy.properties.NumericProperty` @@ -1266,7 +1325,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout): .. rubric:: Center - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-center.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-top.png :align: center .. rubric:: Auto @@ -1282,11 +1341,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout): """ Menu height for selecting the number of displayed rows. - .. rubric:: 140dp - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-140.png - :align: center - .. rubric:: 240dp .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png @@ -1298,7 +1352,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout): background_color = ColorProperty([0, 0, 0, 0]) """ - Background color in the format (r, g, b, a). + Background color in the format (r, g, b, a) or string format. See :attr:`~kivy.uix.modalview.ModalView.background_color`. Use markup strings @@ -1315,6 +1369,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout): class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + layout = AnchorLayout() data_tables = MDDataTable( size_hint=(0.9, 0.6), @@ -1354,7 +1411,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout): background_color_header = ColorProperty(None) """ - Background color for :class:`~TableHeader` class. + Background color in the format (r, g, b, a) or string format for + :class:`~TableHeader` class. .. versionadded:: 1.0.0 @@ -1374,7 +1432,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout): background_color_cell = ColorProperty(None) """ - Background color for :class:`~CellRow` class. + Background color in the format (r, g, b, a) or string format for + :class:`~CellRow` class. .. versionadded:: 1.0.0 @@ -1395,7 +1454,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout): background_color_selected_cell = ColorProperty(None) """ - Background selected color for :class:`~CellRow` class. + Background selected color in the format (r, g, b, a) or string format for + :class:`~CellRow` class. .. versionadded:: 1.0.0 @@ -1408,7 +1468,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout): background_color_selected_cell="e4514f", ) - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.png :align: center :attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and @@ -1503,6 +1563,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout): data_tables = None def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + layout = MDFloatLayout() # root layout # Creating control buttons. button_box = MDBoxLayout( @@ -1604,6 +1667,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout): data_tables = None def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + layout = MDFloatLayout() layout.add_widget( MDRaisedButton( diff --git a/sbapp/kivymd/uix/dialog/dialog.kv b/sbapp/kivymd/uix/dialog/dialog.kv index 1f057a7..96e01a3 100644 --- a/sbapp/kivymd/uix/dialog/dialog.kv +++ b/sbapp/kivymd/uix/dialog/dialog.kv @@ -18,7 +18,11 @@ PopMatrix - + + shadow_color: 0.0, 0.0, 0.0, 0.0 + elevation: 0 + shadow_softness: 0 + shadow_offset: 0, 0 @@ -28,7 +32,6 @@ orientation: "vertical" size_hint_y: None height: self.minimum_height - elevation: 24 padding: "24dp", "24dp", "8dp", "8dp" radius: root.radius md_bg_color: diff --git a/sbapp/kivymd/uix/dialog/dialog.py b/sbapp/kivymd/uix/dialog/dialog.py index 10f4d67..f254f30 100755 --- a/sbapp/kivymd/uix/dialog/dialog.py +++ b/sbapp/kivymd/uix/dialog/dialog.py @@ -38,6 +38,8 @@ Usage dialog = None def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def show_alert_dialog(self): @@ -87,6 +89,7 @@ from kivy.uix.modalview import ModalView from kivymd import uix_path from kivymd.material_resources import DEVICE_TYPE from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import CommonElevationBehavior from kivymd.uix.button import BaseButton from kivymd.uix.card import MDSeparator from kivymd.uix.list import BaseListItem @@ -97,7 +100,40 @@ with open( Builder.load_string(kv_file.read()) -class BaseDialog(ThemableBehavior, ModalView): +class BaseDialog(ThemableBehavior, ModalView, CommonElevationBehavior): + elevation = NumericProperty(3) + """ + See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation` + attribute for more information. + + .. versionadded:: 1.1.0 + + :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` + and defaults to `3`. + """ + + shadow_softness = NumericProperty(24) + """ + See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness` + attribute for more information. + + .. versionadded:: 1.1.0 + + :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` + and defaults to `24`. + """ + + shadow_offset = ListProperty((0, 4)) + """ + See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset` + attribute for more information. + + .. versionadded:: 1.1.0 + + :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` + and defaults to `[0, 4]`. + """ + radius = ListProperty([dp(7), dp(7), dp(7), dp(7)]) """ Dialog corners rounding value. @@ -250,21 +286,22 @@ class MDDialog(BaseDialog): class Example(MDApp): dialog = None - def build(self): - return Builder.load_string(KV) + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def show_simple_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Set backup account", - type="simple", - items=[ - Item(text="user01@gmail.com", source="user-1.png"), - Item(text="user02@gmail.com", source="user-2.png"), - Item(text="Add account", source="add-icon.png"), - ], - ) - self.dialog.open() + def show_simple_dialog(self): + if not self.dialog: + self.dialog = MDDialog( + title="Set backup account", + type="simple", + items=[ + Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"), + Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"), + ], + ) + self.dialog.open() Example().run() @@ -317,6 +354,8 @@ class MDDialog(BaseDialog): dialog = None def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def show_confirmation_dialog(self): @@ -385,71 +424,140 @@ class MDDialog(BaseDialog): """ Custom content class. - .. code-block:: python + .. tabs:: - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog + .. code-block:: python - KV = ''' - - orientation: "vertical" - spacing: "12dp" - size_hint_y: None - height: "120dp" + from kivy.lang import Builder + from kivy.uix.boxlayout import BoxLayout - MDTextField: - hint_text: "City" + from kivymd.app import MDApp + from kivymd.uix.button import MDFlatButton + from kivymd.uix.dialog import MDDialog - MDTextField: - hint_text: "Street" + KV = ''' + + orientation: "vertical" + spacing: "12dp" + size_hint_y: None + height: "120dp" + + MDTextField: + hint_text: "City" + + MDTextField: + hint_text: "Street" - MDFloatLayout: + MDFloatLayout: - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_confirmation_dialog() - ''' + MDFlatButton: + text: "ALERT DIALOG" + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_confirmation_dialog() + ''' - class Content(BoxLayout): - pass + class Content(BoxLayout): + pass - class Example(MDApp): - dialog = None + class Example(MDApp): + dialog = None - def build(self): - return Builder.load_string(KV) + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def show_confirmation_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Address:", - type="custom", - content_cls=Content(), - buttons=[ - MDFlatButton( - text="CANCEL", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - MDFlatButton( - text="OK", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - ], - ) - self.dialog.open() + def show_confirmation_dialog(self): + if not self.dialog: + self.dialog = MDDialog( + title="Address:", + type="custom", + content_cls=Content(), + buttons=[ + MDFlatButton( + text="CANCEL", + theme_text_color="Custom", + text_color=self.theme_cls.primary_color, + ), + MDFlatButton( + text="OK", + theme_text_color="Custom", + text_color=self.theme_cls.primary_color, + ), + ], + ) + self.dialog.open() - Example().run() + Example().run() + + .. tab:: Declarative Python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDFlatButton + from kivymd.uix.dialog import MDDialog + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.textfield import MDTextField + + + class Example(MDApp): + dialog = None + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDFloatLayout( + MDFlatButton( + text="ALERT DIALOG", + pos_hint={'center_x': 0.5, 'center_y': 0.5}, + on_release=self.show_confirmation_dialog, + ) + ) + ) + + def show_confirmation_dialog(self, *args): + if not self.dialog: + self.dialog = MDDialog( + title="Address:", + type="custom", + content_cls=MDBoxLayout( + MDTextField( + hint_text="City", + ), + MDTextField( + hint_text="Street", + ), + orientation="vertical", + spacing="12dp", + size_hint_y=None, + height="120dp", + ), + buttons=[ + MDFlatButton( + text="CANCEL", + theme_text_color="Custom", + text_color=self.theme_cls.primary_color, + ), + MDFlatButton( + text="OK", + theme_text_color="Custom", + text_color=self.theme_cls.primary_color, + ), + ], + ) + self.dialog.open() + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png :align: center @@ -460,7 +568,7 @@ class MDDialog(BaseDialog): md_bg_color = ColorProperty(None) """ - Background color in the format (r, g, b, a). + Background color in the (r, g, b, a) or string format. :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. diff --git a/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv b/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv index 4e6d163..82960e3 100644 --- a/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv +++ b/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv @@ -1,7 +1,7 @@ <_Triangle>: canvas: Color: - rgba: root.theme_cls.text_color + rgba: app.theme_cls.text_color Triangle: points: [ \ @@ -13,7 +13,8 @@ orientation: "vertical" - adaptive_size: True + size_hint: None, None + size: self.minimum_size spacing: "5dp" padding: "5dp", "5dp", "5dp", 0 diff --git a/sbapp/kivymd/uix/dropdownitem/dropdownitem.py b/sbapp/kivymd/uix/dropdownitem/dropdownitem.py index e8a98fb..3967c5c 100644 --- a/sbapp/kivymd/uix/dropdownitem/dropdownitem.py +++ b/sbapp/kivymd/uix/dropdownitem/dropdownitem.py @@ -15,13 +15,13 @@ Usage from kivymd.app import MDApp KV = ''' - Screen + MDScreen MDDropDownItem: id: drop_item pos_hint: {'center_x': .5, 'center_y': .5} text: 'Item' - on_release: self.set_item("New Item") + on_release: print("Press item") ''' @@ -48,12 +48,12 @@ import os from kivy.lang import Builder from kivy.properties import NumericProperty, StringProperty from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout from kivy.uix.widget import Widget from kivymd import uix_path from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior -from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.behaviors import DeclarativeBehavior with open( os.path.join(uix_path, "dropdownitem", "dropdownitem.kv"), encoding="utf-8" @@ -61,15 +61,12 @@ with open( Builder.load_string(kv_file.read()) -class _Triangle(ThemableBehavior, Widget): +class _Triangle(Widget): pass class MDDropDownItem( - ThemableBehavior, - FakeRectangularElevationBehavior, - ButtonBehavior, - MDBoxLayout, + DeclarativeBehavior, ThemableBehavior, ButtonBehavior, BoxLayout ): text = StringProperty() """ diff --git a/sbapp/kivymd/uix/expansionpanel/expansionpanel.py b/sbapp/kivymd/uix/expansionpanel/expansionpanel.py index 8a10a4a..2bbd8ee 100755 --- a/sbapp/kivymd/uix/expansionpanel/expansionpanel.py +++ b/sbapp/kivymd/uix/expansionpanel/expansionpanel.py @@ -56,6 +56,8 @@ Example .. code-block:: python + import os + from kivy.lang import Builder from kivymd.app import MDApp @@ -75,7 +77,7 @@ Example icon: 'phone' - ScrollView: + MDScrollView: MDGridLayout: id: box @@ -96,7 +98,7 @@ Example for i in range(10): self.root.ids.box.add_widget( MDExpansionPanel( - icon=f"{images_path}kivymd.png", + icon=os.path.join(images_path, "logo", "kivymd-icon-128.png"), content=Content(), panel_cls=MDExpansionPanelThreeLine( text="Text", diff --git a/sbapp/kivymd/uix/filemanager/filemanager.kv b/sbapp/kivymd/uix/filemanager/filemanager.kv index 57ab89b..ad5704f 100644 --- a/sbapp/kivymd/uix/filemanager/filemanager.kv +++ b/sbapp/kivymd/uix/filemanager/filemanager.kv @@ -6,28 +6,29 @@ background_normal: "" background_down: "" dir_or_file_name: "" + icon_color: 0, 0, 0, 0 _selected: False events_callback: lambda x: None orientation: "vertical" ModifiedOneLineIconListItem: text: root.dir_or_file_name + on_release: root.events_callback(root.path, root) bg_color: self.theme_cls.bg_darkest \ - if root._selected else self.theme_cls.bg_normal - on_release: root.events_callback(root.path, root) + if root._selected else \ + self.theme_cls.bg_normal IconLeftWidget: icon: root.icon - theme_text_color: "Custom" - text_color: self.theme_cls.primary_color + theme_icon_color: "Custom" + icon_color: root.icon_color MDSeparator: - size_hint_y: None - height: self.texture_size[1] + adaptive_height: True shorten: True shorten_from: "center" halign: "center" @@ -61,23 +62,6 @@ text: root.name - - anchor_x: "right" - anchor_y: "bottom" - size_hint_y: None - height: dp(56) - padding: dp(10) - - MDFloatingActionButton: - size_hint: None, None - size:dp(56), dp(56) - icon: root.icon - opposite_colors: True - elevation: 8 - on_release: root.callback() - md_bg_color: root.md_bg_color - - md_bg_color: root.theme_cls.bg_normal @@ -90,7 +74,11 @@ title: root.current_path right_action_items: [["close-box", lambda x: root.exit_manager(1)]] left_action_items: [["chevron-left", lambda x: root.back()]] - elevation: 10 + elevation: 3 + md_bg_color: + app.theme_cls.primary_color \ + if not root.background_color_toolbar else \ + root.background_color_toolbar RecycleView: id: rv diff --git a/sbapp/kivymd/uix/filemanager/filemanager.py b/sbapp/kivymd/uix/filemanager/filemanager.py index ab88234..1b7727e 100755 --- a/sbapp/kivymd/uix/filemanager/filemanager.py +++ b/sbapp/kivymd/uix/filemanager/filemanager.py @@ -9,7 +9,7 @@ Usage .. code-block:: python - path = '/' # path to the directory that will be opened in the file manager + path = os.path.expanduser("~") # path to the directory that will be opened in the file manager file_manager = MDFileManager( exit_manager=self.exit_manager, # function called when the user reaches directory tree root select_path=self.select_path, # function called when selecting a file/directory @@ -19,7 +19,7 @@ Usage .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png :align: center -.. warning:: Be careful! To use the `/` path on Android devices, you need +.. warning:: Be careful! To use the `'/'` path on Android devices, you need special permissions. Therefore, you are likely to get an error. Or with ``preview`` mode: @@ -32,7 +32,7 @@ Or with ``preview`` mode: preview=True, ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-previous.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-preview.png :align: center .. warning:: The `preview` mode is intended only for viewing images and will @@ -43,6 +43,8 @@ Example .. code-block:: python + import os + from kivy.core.window import Window from kivy.lang import Builder @@ -53,19 +55,19 @@ Example KV = ''' MDBoxLayout: - orientation: 'vertical' + orientation: "vertical" MDTopAppBar: title: "MDFileManager" - left_action_items: [['menu', lambda x: None]] - elevation: 10 + left_action_items: [["menu", lambda x: None]] + elevation: 3 MDFloatLayout: MDRoundFlatIconButton: text: "Open manager" icon: "folder" - pos_hint: {'center_x': .5, 'center_y': .6} + pos_hint: {"center_x": .5, "center_y": .5} on_release: app.file_manager_open() ''' @@ -76,23 +78,23 @@ Example Window.bind(on_keyboard=self.events) self.manager_open = False self.file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - preview=True, + exit_manager=self.exit_manager, select_path=self.select_path ) def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def file_manager_open(self): - self.file_manager.show('/') # output manager to the screen + self.file_manager.show(os.path.expanduser("~")) # output manager to the screen self.manager_open = True - def select_path(self, path): - '''It will be called when you click on the file name + def select_path(self, path: str): + ''' + It will be called when you click on the file name or the catalog selection button. - :type path: str; :param path: path to the selected directory or file; ''' @@ -126,6 +128,9 @@ Not tested on `iOS`. def file_manager_open(self): self.file_manager.show_disks() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-show-disks.png + :align: center """ __all__ = ("MDFileManager",) @@ -136,6 +141,7 @@ import re from typing import List, Tuple, Union from kivy import platform +from kivy.clock import Clock from kivy.factory import Factory from kivy.lang import Builder from kivy.metrics import dp @@ -148,7 +154,6 @@ from kivy.properties import ( OptionProperty, StringProperty, ) -from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.behaviors import ButtonBehavior from kivy.uix.modalview import ModalView @@ -156,9 +161,10 @@ from kivymd import images_path, uix_path from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import CircularRippleBehavior from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.list import BaseListItem, ContainerSupport +from kivymd.uix.button import MDFloatingActionButton +from kivymd.uix.fitimage import FitImage +from kivymd.uix.list import BaseListItem from kivymd.uix.relativelayout import MDRelativeLayout -from kivymd.utils.fitimage import FitImage with open( os.path.join(uix_path, "filemanager", "filemanager.kv"), encoding="utf-8" @@ -167,9 +173,7 @@ with open( class BodyManager(MDBoxLayout): - """ - Base class for folders and files icons. - """ + """Base class for folders and files icons.""" class BodyManagerWithPreview(MDBoxLayout): @@ -182,40 +186,146 @@ class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage): """Folder icons/thumbnails images in ``preview`` mode.""" -class FloatButton(AnchorLayout): - callback = ObjectProperty() - md_bg_color = ColorProperty([1, 1, 1, 1]) - icon = StringProperty() - - -class ModifiedOneLineIconListItem(ContainerSupport, BaseListItem): +class ModifiedOneLineIconListItem(BaseListItem): _txt_left_pad = NumericProperty("72dp") _txt_top_pad = NumericProperty("16dp") _txt_bot_pad = NumericProperty("15dp") _num_lines = 1 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(48) -class MDFileManager(ThemableBehavior, MDRelativeLayout): - icon = StringProperty("check") +class MDFileManager(MDRelativeLayout, ThemableBehavior): """ - The icon that will be used on the directory selection button. + Implements a modal dialog with a file manager. + + For more information, see in the + :class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation. + + :Events: + `on_pre_open`: + Called before the MDFileManager is opened. + `on_open`: + Called when the MDFileManager is opened. + `on_pre_dismiss`: + Called before the MDFileManager is closed. + `on_dismiss`: + Called when the MDFileManager is closed. + """ + + icon = StringProperty("check", deprecated=True) + """ + Icon that will be used on the directory selection button. + + .. deprecated:: 1.1.0 + Use :attr:`icon_selection_button` instead. :attr:`icon` is an :class:`~kivy.properties.StringProperty` and defaults to `check`. """ + icon_selection_button = StringProperty("check") + """ + Icon that will be used on the directory selection button. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDFileManager( + ... + icon_selection_button="pencil", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-selection-button.png + :align: center + + :attr:`icon_selection_button` is an :class:`~kivy.properties.StringProperty` + and defaults to `check`. + """ + + background_color_selection_button = ColorProperty(None) + """ + Background color of the current directory/path selection button. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDFileManager( + ... + background_color_selection_button="brown", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-selection-button.png + :align: center + + :attr:`background_color_selection_button` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + background_color_toolbar = ColorProperty(None) + """ + Background color of the file manager toolbar. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDFileManager( + ... + background_color_toolbar="brown", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-toolbar.png + :align: center + + :attr:`background_color_toolbar` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + icon_folder = StringProperty(f"{images_path}folder.png") """ - The icon that will be used for folder icons when using ``preview = True``. + Icon that will be used for folder icons when using ``preview = True``. + + .. code-block:: python + + MDFileManager( + ... + preview=True, + icon_folder="path/to/icon.png", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-folder.png + :align: center :attr:`icon` is an :class:`~kivy.properties.StringProperty` and defaults to `check`. """ + icon_color = ColorProperty(None) + """ + Color of the folder icon when the :attr:`preview` property is set to False. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDFileManager( + ... + preview=False, + icon_color="brown", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-color.png + :align: center + + :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + exit_manager = ObjectProperty(lambda x: None) """ Function called when the user reaches directory tree root. @@ -252,12 +362,12 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): and defaults to `all`. """ - current_path = StringProperty(os.getcwd()) + current_path = StringProperty(os.path.expanduser("~")) """ Current directory. :attr:`current_path` is an :class:`~kivy.properties.StringProperty` - and defaults to `/`. + and defaults to `os.path.expanduser("~")`. """ use_access = BooleanProperty(True) @@ -288,9 +398,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): "name", options=["nothing", "name", "date", "size", "type"] ) """ - It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option - By default, sort by name. - Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. + It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files + by option. By default, sort by name. Available options are: + `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. :attr:`sort_by` is an :class:`~kivy.properties.OptionProperty` and defaults to `name`. @@ -318,29 +428,33 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): """ Contains the list of files that are currently selected. - :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and - defaults to `[]`. + :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` + and defaults to `[]`. + """ + + selection_button = ObjectProperty() + """ + The instance of the directory/path selection button. + + .. versionadded:: 1.1.0 + + :attr:`selection_button` is a read-only :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. """ _window_manager = None _window_manager_open = False - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_pre_open") + self.register_event_type("on_open") + self.register_event_type("on_pre_dismiss") + self.register_event_type("on_dismiss") + toolbar_label = self.ids.toolbar.children[1].children[0] toolbar_label.font_style = "Subtitle1" - if ( - self.selector == "any" - or self.selector == "multi" - or self.selector == "folder" - ): - self.add_widget( - FloatButton( - callback=self.select_directory_on_press_button, - md_bg_color=self.theme_cls.primary_color, - icon=self.icon, - ) - ) + Clock.schedule_once(self._create_selection_button) if self.preview: self.ext = [".png", ".jpg", ".jpeg"] @@ -393,15 +507,7 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): } ) self.ids.rv.data = manager_list - - if not self._window_manager: - self._window_manager = ModalView( - size_hint=self.size_hint, auto_dismiss=False - ) - self._window_manager.add_widget(self) - if not self._window_manager_open: - self._window_manager.open() - self._window_manager_open = True + self._show() def show(self, path: str) -> None: """ @@ -467,6 +573,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): "icon": icon, "dir_or_file_name": name, "events_callback": self.select_dir_or_file, + "icon_color": self.theme_cls.primary_color + if not self.icon_color + else self.icon_color, "_selected": False, } ) @@ -481,19 +590,14 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): "icon": "file-outline", "dir_or_file_name": os.path.split(name)[1], "events_callback": self.select_dir_or_file, + "icon_color": self.theme_cls.primary_color + if not self.icon_color + else self.icon_color, "_selected": False, } ) self.ids.rv.data = manager_list - - if not self._window_manager: - self._window_manager = ModalView( - size_hint=self.size_hint, auto_dismiss=False - ) - self._window_manager.add_widget(self) - if not self._window_manager_open: - self._window_manager.open() - self._window_manager_open = True + self._show() def get_access_string(self, path: str) -> str: access_string = "" @@ -550,7 +654,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): def close(self) -> None: """Closes the file manager window.""" + self.dispatch("on_pre_dismiss") self._window_manager.dismiss() + self.dispatch("on_dismiss") self._window_manager_open = False def select_dir_or_file( @@ -602,6 +708,84 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout): if self.selector == "folder" or self.selector == "any": self.select_path(self.current_path) + def on_icon(self, instance_file_manager, icon_name: str) -> None: + """Called when the :attr:`icon` property is changed.""" + + self.icon_selection_button = icon_name + + def on_background_color_toolbar( + self, instance_file_manager, color: Union[str, list] + ) -> None: + """ + Called when the :attr:`background_color_toolbar` property is changed. + """ + + def on_background_color_toolbar(*args): + self.ids.toolbar.md_bg_color = color + + Clock.schedule_once(on_background_color_toolbar) + + def on_pre_open(self, *args): + """ + Default pre-open event handler. + + .. versionadded:: 1.1.0 + """ + + def on_open(self, *args): + """ + Default open event handler. + + .. versionadded:: 1.1.0 + """ + + def on_pre_dismiss(self, *args): + """ + Default pre-dismiss event handler. + + .. versionadded:: 1.1.0 + """ + + def on_dismiss(self, *args): + """ + Default dismiss event handler. + + .. versionadded:: 1.1.0 + """ + + def _show(self): + if not self._window_manager: + self._window_manager = ModalView( + size_hint=self.size_hint, auto_dismiss=False + ) + self.size_hint = (1, 1) + self._window_manager.add_widget(self) + + if not self._window_manager_open: + self._window_manager.open() + self._window_manager_open = True + + self.dispatch("on_pre_open") + self.dispatch("on_open") + + def _create_selection_button(self, *args): + if ( + self.selector == "any" + or self.selector == "multi" + or self.selector == "folder" + ): + self.selection_button = MDFloatingActionButton( + on_release=self.select_directory_on_press_button, + md_bg_color=self.theme_cls.primary_color + if not self.background_color_selection_button + else self.background_color_selection_button, + icon=self.icon_selection_button, + pos_hint={"right": 0.99}, + y=dp(12), + elevation=0, + ) + self.add_widget(self.selection_button) + def __sort_files(self, files): def sort_by_name(files): files.sort(key=locale.strxfrm) diff --git a/sbapp/kivymd/uix/fitimage/fitimage.py b/sbapp/kivymd/uix/fitimage/fitimage.py index f3c94ee..35ded4d 100644 --- a/sbapp/kivymd/uix/fitimage/fitimage.py +++ b/sbapp/kivymd/uix/fitimage/fitimage.py @@ -1,6 +1,6 @@ """ Components/FitImage -================== +=================== Feature to automatically crop a `Kivy` image to fit your layout Write by Benedikt Zwölfer @@ -11,20 +11,42 @@ Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad Example: ======== -.. code-block:: kv +.. tabs:: - MDBoxLayout: - size_hint_y: None - height: "200dp" - orientation: 'vertical' + .. tab:: Declarative KV styles - FitImage: - size_hint_y: 3 - source: 'images/img1.jpg' + .. code-block:: kv - FitImage: - size_hint_y: 1 - source: 'images/img2.jpg' + MDBoxLayout: + size_hint_y: None + height: "200dp" + orientation: 'vertical' + + FitImage: + size_hint_y: 3 + source: 'images/img1.jpg' + + FitImage: + size_hint_y: 1 + source: 'images/img2.jpg' + + .. tab:: Declarative python styles + + .. code-block:: python + + MDBoxLayout( + FitImage( + size_hint_y=.3, + source='images/img1.jpg', + ), + FitImage( + size_hint_y=.7, + source='images/img2.jpg', + ), + size_hint_y=None, + height="200dp", + orientation='vertical', + ) Example with round corners: =========================== @@ -32,46 +54,73 @@ Example with round corners: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fitimage-round-corners.png :align: center -.. code-block:: python +.. tabs:: - from kivy.uix.modalview import ModalView - from kivy.lang import Builder + .. tab:: Declarative KV styles - from kivymd import images_path - from kivymd.app import MDApp - from kivymd.uix.card import MDCard + .. code-block:: python - Builder.load_string( - ''' - : - elevation: 10 - radius: [36, ] + from kivy.lang import Builder - FitImage: - id: bg_image - source: "images/bg.png" - size_hint_y: .35 - pos_hint: {"top": 1} - radius: 36, 36, 0, 0 - ''') + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDCard: + radius: 36 + md_bg_color: "grey" + pos_hint: {"center_x": .5, "center_y": .5} + size_hint: .4, .8 + + FitImage: + source: "bg.jpg" + size_hint_y: .35 + pos_hint: {"top": 1} + radius: 36, 36, 0, 0 + ''' - class Card(MDCard): - pass + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - class Example(MDApp): - def build(self): - modal = ModalView( - size_hint=(0.4, 0.8), - background=f"{images_path}/transparent.png", - overlay_color=(0, 0, 0, 0), - ) - modal.add_widget(Card()) - modal.open() + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.card import MDCard + from kivymd.uix.fitimage import FitImage + from kivymd.uix.screen import MDScreen - Example().run() + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + MDScreen( + MDCard( + FitImage( + source="bg.jpg", + size_hint_y=0.35, + pos_hint={"top": 1}, + radius=(36, 36, 0, 0), + ), + radius=36, + md_bg_color="grey", + pos_hint={"center_x": .5, "center_y": .5}, + size_hint=(0.4, 0.8), + ), + ) + ) + + + Example().run() """ __all__ = ("FitImage",) @@ -80,14 +129,14 @@ from kivy.clock import Clock from kivy.graphics.context_instructions import Color from kivy.graphics.vertex_instructions import Rectangle from kivy.properties import BooleanProperty, ObjectProperty -from kivy.uix.boxlayout import BoxLayout from kivy.uix.image import AsyncImage from kivy.uix.widget import Widget -from kivymd.uix.templates import StencilWidget +from kivymd.uix.behaviors import StencilBehavior +from kivymd.uix.boxlayout import MDBoxLayout -class FitImage(BoxLayout, StencilWidget): +class FitImage(MDBoxLayout, StencilBehavior): source = ObjectProperty() """ Filename/source of your image. diff --git a/sbapp/kivymd/uix/floatlayout.py b/sbapp/kivymd/uix/floatlayout.py index df67386..3f99981 100644 --- a/sbapp/kivymd/uix/floatlayout.py +++ b/sbapp/kivymd/uix/floatlayout.py @@ -8,7 +8,7 @@ with some widget properties. For example: FloatLayout ----------- -.. code-block:: +.. code-block:: kv FloatLayout: canvas: @@ -22,7 +22,7 @@ FloatLayout MDFloatLayout ------------- -.. code-block:: +.. code-block:: kv MDFloatLayout: radius: [25, 0, 0, 0] @@ -36,7 +36,11 @@ MDFloatLayout from kivy.uix.floatlayout import FloatLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDFloatLayout(FloatLayout, MDAdaptiveWidget): - pass +class MDFloatLayout(DeclarativeBehavior, FloatLayout, MDAdaptiveWidget): + """ + Float layout class. For more information, see in the + :class:`~kivy.uix.floatlayout.FloatLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/gridlayout.py b/sbapp/kivymd/uix/gridlayout.py index ea9df1e..84d883c 100644 --- a/sbapp/kivymd/uix/gridlayout.py +++ b/sbapp/kivymd/uix/gridlayout.py @@ -86,7 +86,11 @@ Equivalent from kivy.uix.gridlayout import GridLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDGridLayout(GridLayout, MDAdaptiveWidget): - pass +class MDGridLayout(DeclarativeBehavior, GridLayout, MDAdaptiveWidget): + """ + Grid layout class. For more information, see in the + :class:`~kivy.uix.gridlayout.GridLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/hero.py b/sbapp/kivymd/uix/hero.py index 34eef37..70bb572 100644 --- a/sbapp/kivymd/uix/hero.py +++ b/sbapp/kivymd/uix/hero.py @@ -63,7 +63,7 @@ Base example x: 24 FitImage: - source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png" + source: "kivymd/images/logo/kivymd-icon-512.png" size_hint: None, None size: hero_from.size @@ -72,7 +72,7 @@ Base example pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen B" MDScreen: @@ -82,6 +82,7 @@ Base example MDHeroTo: id: hero_to + tag: "hero" size_hint: None, None size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} @@ -91,7 +92,7 @@ Base example pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen A" ''' @@ -113,6 +114,7 @@ Note that the child of the :class:`~MDHeroFrom` widget must have the size of the MDHeroFrom: id: hero_from + tag: "hero" FitImage: size_hint: None, None @@ -127,7 +129,7 @@ container in which the hero is located: MDRaisedButton: text: "Move Hero To Screen B" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen 2" If you need to switch to a screen that does not contain heroes, set the @@ -138,7 +140,7 @@ If you need to switch to a screen that does not contain heroes, set the MDRaisedButton: text: "Go To Another Screen" on_release: - root.current_hero = "" + root.current_heroes = [] root.current = "another screen" Example @@ -166,7 +168,7 @@ Example x: 24 FitImage: - source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png" + source: "kivymd/images/logo/kivymd-icon-512.png" size_hint: None, None size: hero_from.size @@ -175,7 +177,7 @@ Example pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen B" MDScreen: @@ -185,6 +187,7 @@ Example MDHeroTo: id: hero_to + tag: "hero" size_hint: None, None size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} @@ -194,7 +197,7 @@ Example pos_hint: {"center_x": .5} y: "52dp" on_release: - root.current_hero = "" + root.current_heroes = [] root.current = "screen C" MDRaisedButton: @@ -202,7 +205,7 @@ Example pos_hint: {"center_x": .5} y: "8dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen A" MDScreen: @@ -283,7 +286,7 @@ background color of the hero during the flight between the screens: pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen B" MDScreen: @@ -293,6 +296,7 @@ background color of the hero during the flight between the screens: MDHeroTo: id: hero_to + tag: "hero" size_hint: None, None size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} @@ -302,7 +306,7 @@ background color of the hero during the flight between the screens: pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = ["hero"] root.current = "screen A" ''' @@ -370,7 +374,7 @@ Usage with ScrollView radius: 24 box_radius: 0, 0, 24, 24 box_color: 0, 0, 0, .5 - source: "image.jpg" + source: "kivymd/images/logo/kivymd-icon-512.png" size_hint: None, None size: root.size mipmap: True @@ -399,7 +403,7 @@ Usage with ScrollView MDScreen: name: "screen B" - hero_to: hero_to + heroes_to: [hero_to] MDHeroTo: id: hero_to @@ -412,7 +416,7 @@ Usage with ScrollView pos_hint: {"center_x": .5} y: "36dp" on_release: - root.current_hero = "hero" + root.current_heroes = [hero_to.tag] root.current = "screen A" ''' @@ -441,7 +445,8 @@ Usage with ScrollView def on_release(self): def switch_screen(*args): - self.manager.current_hero = self.tag + self.manager.current_heroes = [self.tag] + self.manager.ids.hero_to.tag = self.tag self.manager.current = "screen B" Clock.schedule_once(switch_screen, 0.2) @@ -465,6 +470,93 @@ Usage with ScrollView .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif :align: center + +Using multiple heroes at the same time +-------------------------------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreenManager: + + MDScreen: + name: "screen A" + md_bg_color: "lightblue" + + MDHeroFrom: + id: hero_kivymd + tag: "kivymd" + size_hint: None, None + size: "200dp", "200dp" + pos_hint: {"top": .98} + x: 24 + + FitImage: + source: "kivymd/images/logo/kivymd-icon-512.png" + size_hint: None, None + size: hero_kivymd.size + + MDHeroFrom: + id: hero_kivy + tag: "kivy" + size_hint: None, None + size: "200dp", "200dp" + pos_hint: {"top": .98} + x: 324 + + FitImage: + source: "data/logo/kivy-icon-512.png" + size_hint: None, None + size: hero_kivy.size + + MDRaisedButton: + text: "Move Hero To Screen B" + pos_hint: {"center_x": .5} + y: "36dp" + on_release: + root.current_heroes = ["kivymd", "kivy"] + root.current = "screen B" + + MDScreen: + name: "screen B" + heroes_to: hero_to_kivymd, hero_to_kivy + md_bg_color: "cadetblue" + + MDHeroTo: + id: hero_to_kivy + tag: "kivy" + size_hint: None, None + pos_hint: {"center_x": .5, "center_y": .5} + + MDHeroTo: + id: hero_to_kivymd + tag: "kivymd" + size_hint: None, None + pos_hint: {"right": 1, "top": 1} + + MDRaisedButton: + text: "Move Hero To Screen A" + pos_hint: {"center_x": .5} + y: "36dp" + on_release: + root.current_heroes = ["kivy", "kivymd"] + root.current = "screen A" + ''' + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + + Test().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-multiple-heroes.gif + :align: center """ from kivy.properties import StringProperty @@ -476,6 +568,9 @@ class MDHeroFrom(MDBoxLayout): """ The container from which the hero begins his flight. + For more information, see in the + :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. + :Events: `on_transform_in` when the hero flies from screen **A** to screen **B**. @@ -487,7 +582,7 @@ class MDHeroFrom(MDBoxLayout): """ Tag ID for heroes. - :attr:`shift_right` is an :class:`~kivy.properties.StringProperty` + :attr:`tag` is an :class:`~kivy.properties.StringProperty` and defaults to `''`. """ @@ -504,4 +599,17 @@ class MDHeroFrom(MDBoxLayout): class MDHeroTo(MDBoxLayout): - """The container in which the hero comes.""" + """ + The container in which the hero comes. + + For more information, see in the + :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. + """ + + tag = StringProperty(allownone=True) + """ + Tag ID for heroes. + + :attr:`tag` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ diff --git a/sbapp/kivymd/uix/imagelist/__init__.py b/sbapp/kivymd/uix/imagelist/__init__.py index f8ba305..97c897c 100644 --- a/sbapp/kivymd/uix/imagelist/__init__.py +++ b/sbapp/kivymd/uix/imagelist/__init__.py @@ -1,2 +1,2 @@ # NOQA F401 -from .imagelist import MDSmartTile, SmartTileWithLabel, SmartTileWithStar +from .imagelist import MDSmartTile diff --git a/sbapp/kivymd/uix/imagelist/imagelist.py b/sbapp/kivymd/uix/imagelist/imagelist.py index b858404..970ed2e 100755 --- a/sbapp/kivymd/uix/imagelist/imagelist.py +++ b/sbapp/kivymd/uix/imagelist/imagelist.py @@ -65,12 +65,13 @@ Implementation :align: center """ -__all__ = ("MDSmartTile", "SmartTileWithLabel", "SmartTileWithStar") +__all__ = [ + "MDSmartTile", +] import os from kivy.lang import Builder -from kivy.logger import Logger from kivy.properties import ( BooleanProperty, ColorProperty, @@ -102,7 +103,7 @@ class SmartTileOverlayBox(MDBoxLayout): """Implements a container for custom widgets to be added to the tile.""" -class MDSmartTile(ThemableBehavior, MDRelativeLayout): +class MDSmartTile(MDRelativeLayout, ThemableBehavior): """ A tile for more complex needs. @@ -248,8 +249,8 @@ class MDSmartTile(ThemableBehavior, MDRelativeLayout): and defaults to `False`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_release") self.register_event_type("on_press") @@ -270,33 +271,3 @@ class MDSmartTile(ThemableBehavior, MDRelativeLayout): widget.shorten = True widget.shorten_from = "right" self.ids.box.add_widget(widget) - - -class SmartTileWithLabel(MDSmartTile): - """ - .. deprecated:: 1.0.0 - Use :class:`~MDSmartTile` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `SmartTileWithLabel` class has been deprecated. " - "Use the `MDSmartTile` class instead`" - ) - - -class SmartTileWithStar(MDSmartTile): - """ - .. deprecated:: 1.0.0 - Use :class:`~MDSmartTile` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `SmartTileWithStar` class has been deprecated. " - "Use the `MDSmartTile` class instead`" - ) diff --git a/sbapp/kivymd/uix/label/label.kv b/sbapp/kivymd/uix/label/label.kv index b974336..71cac91 100644 --- a/sbapp/kivymd/uix/label/label.kv +++ b/sbapp/kivymd/uix/label/label.kv @@ -17,8 +17,14 @@ rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) Rectangle: source: self.source if self.source else None - pos: self.pos - size: self.size + pos: + self.pos \ + if not self.source else \ + (self.x - self._size[0] / 2, self.y) + size: + self._size \ + if self.source else \ + self.size font_style: "Icon" text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank" diff --git a/sbapp/kivymd/uix/label/label.py b/sbapp/kivymd/uix/label/label.py index da164f7..fc34ba4 100755 --- a/sbapp/kivymd/uix/label/label.py +++ b/sbapp/kivymd/uix/label/label.py @@ -222,14 +222,18 @@ __all__ = ("MDLabel", "MDIcon") import os from typing import Union +from kivy.animation import Animation from kivy.clock import Clock +from kivy.graphics import Color, Rectangle from kivy.lang import Builder from kivy.metrics import sp from kivy.properties import ( AliasProperty, BooleanProperty, ColorProperty, + ListProperty, NumericProperty, + ObjectProperty, OptionProperty, StringProperty, ) @@ -239,6 +243,7 @@ from kivymd import uix_path from kivymd.theming import ThemableBehavior from kivymd.theming_dynamic_text import get_contrast_text_color from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior from kivymd.uix.floatlayout import MDFloatLayout __MDLabel_colors__ = { @@ -259,7 +264,7 @@ with open( Builder.load_string(kv_file.read()) -class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): +class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget): font_style = StringProperty("Body1") """ Label font style. @@ -321,6 +326,7 @@ class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): parent_background = ColorProperty(None) can_capitalize = BooleanProperty(True) + canvas_bg = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) @@ -348,6 +354,7 @@ class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): font_info = self.theme_cls.font_styles[self.font_style] self.font_name = font_info[0] self.font_size = sp(font_info[1]) + if font_info[2] and self.can_capitalize: self._capitalizing = True else: @@ -373,29 +380,68 @@ class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): # generic None value it's not yet been set self._text_color_str = "" if theme_text_color == "Custom" and self.text_color: - self.color = self.text_color + color = self.text_color elif ( theme_text_color == "ContrastParentBackground" and self.parent_background ): - self.color = get_contrast_text_color(self.parent_background) + color = get_contrast_text_color(self.parent_background) else: - self.color = [0, 0, 0, 1] + color = [0, 0, 0, 1] - def on_text_color(self, instance_label, color: list) -> None: + if self.theme_cls.theme_style_switch_animation: + Animation( + color=color, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self.color = color + + def on_text_color(self, instance_label, color: Union[list, str]) -> None: if self.theme_text_color == "Custom": - self.color = self.text_color + if self.theme_cls.theme_style_switch_animation: + Animation( + color=self.text_color, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self.color = self.text_color def on_opposite_colors(self, *args) -> None: self.on_theme_text_color(self, self.theme_text_color) + def on_md_bg_color(self, instance_label, color: Union[list, str]) -> None: + self.canvas.remove_group("Background_instruction") + with self.canvas.before: + Color(rgba=color) + self.canvas_bg = Rectangle(pos=self.pos, size=self.size) + self.bind(pos=self.update_canvas_bg_pos) + + def on_size(self, instance_label, size: list) -> None: + if self.canvas_bg: + self.canvas_bg.size = size + + def update_canvas_bg_pos(self, instance_label, pos: list) -> None: + if self.canvas_bg: + self.canvas_bg.pos = pos + def _do_update_theme_color(self, *args): if self._text_color_str: - self.color = getattr(self.theme_cls, self._text_color_str) if not self.disabled: - self.color = getattr(self.theme_cls, self._text_color_str) + color = getattr(self.theme_cls, self._text_color_str) else: - self.color = getattr(self.theme_cls, "disabled_hint_text_color") + color = getattr(self.theme_cls, "disabled_hint_text_color") + + if self.theme_cls.theme_style_switch_animation: + Animation( + color=color, + d=self.theme_cls.theme_style_switch_animation_duration, + t="linear", + ).start(self) + else: + self.color = color class MDIcon(MDFloatLayout, MDLabel): @@ -455,11 +501,16 @@ class MDIcon(MDFloatLayout, MDLabel): and defaults to `None`. """ - def __init__(self, **kwargs): + _size = ListProperty((0, 0)) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + Clock.schedule_once(self.adjust_size) + + def adjust_size(self, *args) -> None: from kivymd.uix.selectioncontrol import MDCheckbox - super().__init__(**kwargs) if not isinstance(self, MDCheckbox): self.size_hint = (None, None) - self.size = self.texture_size + self._size = self.texture_size[1], self.texture_size[1] self.adaptive_size = True diff --git a/sbapp/kivymd/uix/list/__init__.py b/sbapp/kivymd/uix/list/__init__.py index b61f9d8..a0e061c 100644 --- a/sbapp/kivymd/uix/list/__init__.py +++ b/sbapp/kivymd/uix/list/__init__.py @@ -2,7 +2,6 @@ from .list import ( BaseListItem, CheckboxLeftWidget, - ContainerSupport, IconLeftWidget, IconLeftWidgetWithoutTouch, IconRightWidget, diff --git a/sbapp/kivymd/uix/list/list.py b/sbapp/kivymd/uix/list/list.py index 75591e4..9f02cab 100755 --- a/sbapp/kivymd/uix/list/list.py +++ b/sbapp/kivymd/uix/list/list.py @@ -57,6 +57,7 @@ based on the above classes. - OneLineAvatarListItem_ - TwoLineAvatarListItem_ - ThreeLineAvatarListItem_ + - OneLineIconListItem_ - TwoLineIconListItem_ - ThreeLineIconListItem_ @@ -68,35 +69,71 @@ based on the above classes. - TwoLineAvatarIconListItem_ - ThreeLineAvatarIconListItem_ +- OneLineRightIconListItem_ +- TwoLineRightIconListItem_ +- ThreeLineRightIconListItem_ + Usage ----- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.list import OneLineListItem + .. code-block:: python - KV = ''' - ScrollView: + from kivy.lang import Builder - MDList: - id: container - ''' + from kivymd.app import MDApp + from kivymd.uix.list import OneLineListItem + + KV = ''' + MDScrollView: + + MDList: + id: container + ''' - class Test(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - def on_start(self): - for i in range(20): - self.root.ids.container.add_widget( - OneLineListItem(text=f"Single-line item {i}") - ) + def on_start(self): + for i in range(20): + self.root.ids.container.add_widget( + OneLineListItem(text=f"Single-line item {i}") + ) - Test().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.list import OneLineListItem + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + MDScrollView( + MDList( + id="container" + ) + ) + ) + + def on_start(self): + for i in range(20): + self.root.ids.container.add_widget( + OneLineListItem(text=f"Single-line item {i}") + ) + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.gif :align: center @@ -104,37 +141,76 @@ Usage Events of List -------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp + .. code-block:: python - KV = ''' - ScrollView: + from kivy.lang import Builder - MDList: + from kivymd.app import MDApp - OneLineAvatarIconListItem: - on_release: print("Click!") + KV = ''' + MDScrollView: - IconLeftWidget: - icon: "github" + MDList: - OneLineAvatarIconListItem: - on_release: print("Click 2!") + OneLineAvatarIconListItem: + on_release: print("Click!") - IconLeftWidget: - icon: "gitlab" - ''' + IconLeftWidget: + icon: "github" + + OneLineAvatarIconListItem: + on_release: print("Click 2!") + + IconLeftWidget: + icon: "gitlab" + ''' - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - MainApp().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.list import MDList, OneLineAvatarIconListItem, IconLeftWidget + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + MDScrollView( + MDList( + OneLineAvatarIconListItem( + IconLeftWidget( + icon="github" + ), + on_release=lambda x: print("Click!") + ), + OneLineAvatarIconListItem( + IconLeftWidget( + icon="gitlab" + ), + on_release=lambda x: print("Click 2!") + ), + ) + ) + ) + + + Example().run() .. OneLineListItem: OneLineListItem @@ -179,62 +255,220 @@ ThreeLineListItem OneLineAvatarListItem --------------------- -.. code-block:: kv +.. tabs:: - OneLineAvatarListItem: - text: "Single-line item with avatar" + .. tab:: Declarative KV style - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" + .. code-block:: kv -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists-map.png + OneLineAvatarListItem: + text: "Single-line item with avatar" + + ImageLeftWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineAvatarListItem( + ImageLeftWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + ) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarListItem.png :align: center .. TwoLineAvatarListItem: TwoLineAvatarListItem --------------------- -.. code-block:: kv +.. tabs:: - TwoLineAvatarListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" + .. tab:: Declarative KV style - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" + .. code-block:: kv + + TwoLineAvatarListItem: + text: "Two-line item with avatar" + secondary_text: "Secondary text here" + + ImageLeftWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineAvatarListItem( + ImageLeftWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarListItem.png :align: center - .. ThreeLineAvatarListItem: ThreeLineAvatarListItem ----------------------- -.. code-block:: kv +.. tabs:: - ThreeLineAvatarListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" + .. tab:: Declarative KV style - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" + .. code-block:: kv + + ThreeLineAvatarListItem: + text: "Three-line item with avatar" + secondary_text: "Secondary text here" + tertiary_text: "fit more text than usual" + + ImageLeftWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineAvatarListItem( + ImageLeftWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + tertiary_text: "fit more text than usual" + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarListItem.png :align: center +.. OneLineRightIconListItem: +OneLineRightIconListItem +------------------------ + +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: kv + + OneLineRightIconListItem: + text: "Single-line item with avatar" + + ImageRightWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineRightIconListItem( + ImageRightWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + ) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineRightIconListItem.png + :align: center + +.. TwoLineRightIconListItem: +TwoLineRightIconListItem +------------------------ + +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: kv + + TwoLineRightIconListItem: + text: "Single-line item with avatar" + secondary_text: "Secondary text here" + + ImageRightWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + TwoLineRightIconListItem( + ImageRightWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + ) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineRightIconListItem.png + :align: center + +.. ThreeLineRightIconListItem: +ThreeLineRightIconListItem +-------------------------- + +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: kv + + ThreeLineRightIconListItem: + text: "Single-line item with avatar" + secondary_text: "Secondary text here" + tertiary_text: "fit more text than usual" + + ImageRightWidget: + source: "kivymd/images/logo/kivymd-icon-256.png" + + .. tab:: Declarative python style + + .. code-block:: python + + ThreeLineRightIconListItem( + ImageRightWidget( + source="kivymd/images/logo/kivymd-icon-256.png" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + tertiary_text: "fit more text than usual", + ) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineRightIconListItem.png + :align: center + .. OneLineIconListItem: OneLineIconListItem ------------------- -.. code-block:: kv +.. tabs:: - OneLineIconListItem: - text: "Single-line item with avatar" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "language-python" + .. code-block:: kv + + OneLineIconListItem: + text: "Single-line item with avatar" + + IconLeftWidget: + icon: "language-python" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineIconListItem( + IconLeftWidget( + icon="language-python" + ), + text="Single-line item with avatar" + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineIconListItem.png :align: center @@ -243,14 +477,30 @@ OneLineIconListItem TwoLineIconListItem ------------------- -.. code-block:: kv +.. tabs:: - TwoLineIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "language-python" + .. code-block:: kv + + TwoLineIconListItem: + text: "Two-line item with avatar" + secondary_text: "Secondary text here" + + IconLeftWidget: + icon: "language-python" + + .. tab:: Declarative python style + + .. code-block:: python + + TwoLineIconListItem( + IconLeftWidget( + icon="language-python" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here" + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineIconListItem.png :align: center @@ -259,15 +509,32 @@ TwoLineIconListItem ThreeLineIconListItem --------------------- -.. code-block:: kv +.. tabs:: - ThreeLineIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "language-python" + .. code-block:: kv + + ThreeLineIconListItem: + text: "Three-line item with avatar" + secondary_text: "Secondary text here" + tertiary_text: "fit more text than usual" + + IconLeftWidget: + icon: "language-python" + + .. tab:: Declarative python style + + .. code-block:: python + + ThreeLineIconListItem( + IconLeftWidget( + icon="language-python" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + tertiary_text: "fit more text than usual", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineIconListItem.png :align: center @@ -276,16 +543,34 @@ ThreeLineIconListItem OneLineAvatarIconListItem ------------------------- -.. code-block:: kv +.. tabs:: - OneLineAvatarIconListItem: - text: "One-line item with avatar" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "plus" + .. code-block:: kv - IconRightWidget: - icon: "minus" + OneLineAvatarIconListItem: + text: "One-line item with avatar" + + IconLeftWidget: + icon: "plus" + + IconRightWidget: + icon: "minus" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineAvatarIconListItem( + IconLeftWidget( + icon="plus" + ), + IconRightWidget( + icon="minus" + ), + text="Single-line item with avatar", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarIconListItem.png :align: center @@ -294,17 +579,36 @@ OneLineAvatarIconListItem TwoLineAvatarIconListItem ------------------------- -.. code-block:: kv +.. tabs:: - TwoLineAvatarIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "plus" + .. code-block:: kv - IconRightWidget: - icon: "minus" + TwoLineAvatarIconListItem: + text: "Two-line item with avatar" + secondary_text: "Secondary text here" + + IconLeftWidget: + icon: "plus" + + IconRightWidget: + icon: "minus" + + .. tab:: Declarative python style + + .. code-block:: python + + TwoLineAvatarIconListItem( + IconLeftWidget( + icon="plus" + ), + IconRightWidget( + icon="minus" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarIconListItem.png :align: center @@ -313,18 +617,38 @@ TwoLineAvatarIconListItem ThreeLineAvatarIconListItem --------------------------- -.. code-block:: kv +.. tabs:: - ThreeLineAvatarIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "plus" + .. code-block:: kv - IconRightWidget: - icon: "minus" + ThreeLineAvatarIconListItem: + text: "Three-line item with avatar" + secondary_text: "Secondary text here" + tertiary_text: "fit more text than usual" + + IconLeftWidget: + icon: "plus" + + IconRightWidget: + icon: "minus" + + .. tab:: Declarative python style + + .. code-block:: python + + ThreeLineAvatarIconListItem( + IconLeftWidget( + icon="plus" + ), + IconRightWidget( + icon="minus" + ), + text="Single-line item with avatar", + secondary_text: "Secondary text here", + tertiary_text: "fit more text than usual", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarIconListItem.png :align: center @@ -332,101 +656,196 @@ ThreeLineAvatarIconListItem Custom list item ---------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import StringProperty + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem - from kivymd.uix.selectioncontrol import MDCheckbox - from kivymd.icon_definitions import md_icons + .. code-block:: python + + from kivy.lang import Builder + from kivy.properties import StringProperty + + from kivymd.app import MDApp + from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem + from kivymd.uix.selectioncontrol import MDCheckbox + from kivymd.icon_definitions import md_icons - KV = ''' - : + KV = ''' + : - IconLeftWidget: - icon: root.icon + IconLeftWidget: + icon: root.icon - RightCheckbox: + RightCheckbox: - MDBoxLayout: + MDScrollView: - ScrollView: - - MDList: - id: scroll - ''' + MDList: + id: scroll + ''' - class ListItemWithCheckbox(OneLineAvatarIconListItem): - '''Custom list item.''' + class ListItemWithCheckbox(OneLineAvatarIconListItem): + '''Custom list item.''' - icon = StringProperty("android") + icon = StringProperty("android") - class RightCheckbox(IRightBodyTouch, MDCheckbox): - '''Custom right container.''' + class RightCheckbox(IRightBodyTouch, MDCheckbox): + '''Custom right container.''' - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - def on_start(self): - icons = list(md_icons.keys()) - for i in range(30): - self.root.ids.scroll.add_widget( - ListItemWithCheckbox(text=f"Item {i}", icon=icons[i]) - ) + def on_start(self): + icons = list(md_icons.keys()) + for i in range(30): + self.root.ids.scroll.add_widget( + ListItemWithCheckbox(text=f"Item {i}", icon=icons[i]) + ) - MainApp().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem + from kivymd.uix.selectioncontrol import MDCheckbox + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.list import MDList + from kivymd.icon_definitions import md_icons + + + class RightCheckbox(IRightBodyTouch, MDCheckbox): + '''Custom right container.''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + MDScrollView( + MDList( + id="scroll" + ) + ) + ) + + def on_start(self): + icons = list(md_icons.keys()) + for i in range(30): + self.root.ids.scroll.add_widget( + OneLineAvatarIconListItem( + IconLeftWidget( + icon=icons[i] + ), + RightCheckbox(), + text=f"Item {i}", + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-item.png :align: center -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.list import IRightBodyTouch + .. code-block:: python - KV = ''' - OneLineAvatarIconListItem: - text: "One-line item with avatar" - on_size: - self.ids._right_container.width = container.width - self.ids._right_container.x = container.width + from kivy.lang import Builder - IconLeftWidget: - icon: "cog" + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.list import IRightBodyTouch - YourContainer: - id: container + KV = ''' + OneLineAvatarIconListItem: + text: "One-line item with avatar" + on_size: + self.ids._right_container.width = container.width + self.ids._right_container.x = container.width - MDIconButton: - icon: "minus" + IconLeftWidget: + icon: "cog" - MDIconButton: - icon: "plus" - ''' + YourContainer: + id: container + + MDIconButton: + icon: "minus" + + MDIconButton: + icon: "plus" + ''' - class YourContainer(IRightBodyTouch, MDBoxLayout): - adaptive_width = True + class YourContainer(IRightBodyTouch, MDBoxLayout): + adaptive_width = True - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - MainApp().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.list import IRightBodyTouch + from kivymd.uix.button import MDIconButton + from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget + + + class YourContainer(IRightBodyTouch, MDBoxLayout): + adaptive_width = True + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return ( + OneLineAvatarIconListItem( + IconLeftWidget( + icon="cog" + ), + YourContainer( + MDIconButton( + icon="minus" + ), + MDIconButton( + icon="plus" + ), + id="container" + ), + text="One-line item with avatar" + ) + ) + + def on_start(self): + container = self.root.ids.container + self.root.ids._right_container.width = container.width + container.x = container.width + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-right-container.png :align: center @@ -437,26 +856,56 @@ Behavior When using the `AvatarListItem` and `IconListItem` classes, when an icon is clicked, the event of this icon is triggered: -.. code-block:: kv +.. tabs:: - OneLineIconListItem: - text: "Single-line item with icon" + .. tab:: Declarative KV style - IconLeftWidget: - icon: "language-python" + .. code-block:: kv + + OneLineIconListItem: + text: "Single-line item with icon" + + IconLeftWidget: + icon: "language-python" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineIconListItem( + IconLeftWidget( + icon="language-python" + ), + text="Single-line item with avatar", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-trigger.gif :align: center You can disable the icon event using the `WithoutTouch` classes: -.. code-block:: kv +.. tabs:: - OneLineIconListItem: - text: "Single-line item with icon" + .. tab:: Declarative KV style - IconLeftWidgetWithoutTouch: - icon: "language-python" + .. code-block:: kv + + OneLineIconListItem: + text: "Single-line item with icon" + + IconLeftWidgetWithoutTouch: + icon: "language-python" + + .. tab:: Declarative python style + + .. code-block:: python + + OneLineIconListItem( + IconLeftWidgetWithoutTouch( + icon="language-python" + ), + text="Single-line item with avatar", + ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-without-trigger.gif :align: center @@ -504,21 +953,23 @@ from kivy.properties import ( NumericProperty, OptionProperty, StringProperty, + VariableListProperty, ) from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.floatlayout import FloatLayout import kivymd.material_resources as m_res from kivymd import uix_path from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( CircularRippleBehavior, + DeclarativeBehavior, RectangularRippleBehavior, ) from kivymd.uix.button import MDIconButton -from kivymd.uix.floatlayout import MDFloatLayout +from kivymd.uix.fitimage import FitImage from kivymd.uix.gridlayout import MDGridLayout from kivymd.uix.selectioncontrol import MDCheckbox -from kivymd.utils.fitimage import FitImage with open( os.path.join(uix_path, "list", "list.kv"), encoding="utf-8" @@ -537,17 +988,17 @@ class MDList(MDGridLayout): _list_vertical_padding = NumericProperty("8dp") - def add_widget(self, widget, index=0, canvas=None): - super().add_widget(widget, index, canvas) - self.height += widget.height - - def remove_widget(self, widget): - super().remove_widget(widget) - self.height -= widget.height + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.adaptive_height = True class BaseListItem( - ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDFloatLayout + DeclarativeBehavior, + ThemableBehavior, + RectangularRippleBehavior, + ButtonBehavior, + FloatLayout, ): """ Base class to all ListItems. Not supposed to be instantiated on its own. @@ -563,8 +1014,8 @@ class BaseListItem( text_color = ColorProperty(None) """ - Text color in ``rgba`` format used if :attr:`~theme_text_color` is set - to `'Custom'`. + Text color in (r, g, b, a) or string format used + if :attr:`~theme_text_color` is set to `'Custom'`. :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -572,7 +1023,9 @@ class BaseListItem( font_style = StringProperty("Subtitle1") """ - Text font style. See ``kivymd.font_definitions.py``. + Text font style. + See `font-definitions `_ + for more information. :attr:`font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Subtitle1'`. @@ -580,7 +1033,7 @@ class BaseListItem( theme_text_color = StringProperty("Primary", allownone=True) """ - Theme text color in ``rgba`` format for primary text. + The name of the color scheme for for the primary text. :attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Primary'`. @@ -604,7 +1057,7 @@ class BaseListItem( secondary_text_color = ColorProperty(None) """ - Text color in ``rgba`` format used for secondary text + Text color in (r, g, b, a) or string format used for secondary text if :attr:`~secondary_theme_text_color` is set to `'Custom'`. :attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty` @@ -613,7 +1066,7 @@ class BaseListItem( tertiary_text_color = ColorProperty(None) """ - Text color in ``rgba`` format used for tertiary text + Text color in (r, g, b, a) or string format used for tertiary text if :attr:`~tertiary_theme_text_color` is set to 'Custom'. :attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty` @@ -622,7 +1075,7 @@ class BaseListItem( secondary_theme_text_color = StringProperty("Secondary", allownone=True) """ - Theme text color for secondary text. + The name of the color scheme for for the secondary text. :attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Secondary'`. @@ -630,7 +1083,7 @@ class BaseListItem( tertiary_theme_text_color = StringProperty("Secondary", allownone=True) """ - Theme text color for tertiary text. + The name of the color scheme for for the tertiary text. :attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Secondary'`. @@ -638,7 +1091,9 @@ class BaseListItem( secondary_font_style = StringProperty("Body1") """ - Font style for secondary line. See ``kivymd.font_definitions.py``. + Font style for secondary line. + See `font-definitions `_ + for more information. :attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Body1'`. @@ -646,7 +1101,9 @@ class BaseListItem( tertiary_font_style = StringProperty("Body1") """ - Font style for tertiary line. See ``kivymd.font_definitions.py``. + Font style for tertiary line. + See `font-definitions `_ + for more information. :attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Body1'`. @@ -665,7 +1122,7 @@ class BaseListItem( divider_color = ColorProperty(None) """ - Divider color. + Divider color in (r, g, b, a) or string format. .. versionadded:: 1.0.0 @@ -675,18 +1132,81 @@ class BaseListItem( bg_color = ColorProperty(None) """ - Background color for menu item. + Background color for list item in (r, g, b, a) or string format. :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ + radius = VariableListProperty([0], length=4) + """ + Canvas radius. + + .. code-block:: python + + # Top left corner slice. + MDBoxLayout: + md_bg_color: app.theme_cls.primary_color + radius: [25, 0, 0, 0] + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ + _txt_left_pad = NumericProperty("16dp") _txt_top_pad = NumericProperty() _txt_bot_pad = NumericProperty() _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS) _num_lines = 3 _no_ripple_effect = BooleanProperty(False) + _touchable_widgets = ListProperty() + + def on_touch_down(self, touch): + if self.propagate_touch_to_touchable_widgets(touch, "down"): + return + super().on_touch_down(touch) + + def on_touch_move(self, touch, *args): + if self.propagate_touch_to_touchable_widgets(touch, "move", *args): + return + super().on_touch_move(touch, *args) + + def on_touch_up(self, touch): + if self.propagate_touch_to_touchable_widgets(touch, "up"): + return + super().on_touch_up(touch) + + def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args): + triggered = False + for i in self._touchable_widgets: + if i.collide_point(touch.x, touch.y): + triggered = True + if touch_event == "down": + i.on_touch_down(touch) + elif touch_event == "move": + i.on_touch_move(touch, *args) + elif touch_event == "up": + i.on_touch_up(touch) + return triggered + + def add_widget(self, widget): + if issubclass(widget.__class__, ILeftBody): + self.ids._left_container.add_widget(widget) + elif issubclass(widget.__class__, ILeftBodyTouch): + self.ids._left_container.add_widget(widget) + self._touchable_widgets.append(widget) + elif issubclass(widget.__class__, IRightBody): + self.ids._right_container.add_widget(widget) + elif issubclass(widget.__class__, IRightBodyTouch): + self.ids._right_container.add_widget(widget) + self._touchable_widgets.append(widget) + else: + return super().add_widget(widget) + + def remove_widget(self, widget): + super().remove_widget(widget) + if widget in self._touchable_widgets: + self._touchable_widgets.remove(widget) class ILeftBody: @@ -721,72 +1241,16 @@ class IRightBodyTouch: """ -class ContainerSupport: - """ - Overrides ``add_widget`` in a ``ListItem`` to include support - for ``I*Body`` widgets when the appropiate containers are present. - """ - - _touchable_widgets = ListProperty() - - def add_widget(self, widget, index=0): - if issubclass(widget.__class__, ILeftBody): - self.ids._left_container.add_widget(widget) - elif issubclass(widget.__class__, ILeftBodyTouch): - self.ids._left_container.add_widget(widget) - self._touchable_widgets.append(widget) - elif issubclass(widget.__class__, IRightBody): - self.ids._right_container.add_widget(widget) - elif issubclass(widget.__class__, IRightBodyTouch): - self.ids._right_container.add_widget(widget) - self._touchable_widgets.append(widget) - else: - return super().add_widget(widget) - - def remove_widget(self, widget): - super().remove_widget(widget) - if widget in self._touchable_widgets: - self._touchable_widgets.remove(widget) - - def on_touch_down(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "down"): - return - super().on_touch_down(touch) - - def on_touch_move(self, touch, *args): - if self.propagate_touch_to_touchable_widgets(touch, "move", *args): - return - super().on_touch_move(touch, *args) - - def on_touch_up(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "up"): - return - super().on_touch_up(touch) - - def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args): - triggered = False - for i in self._touchable_widgets: - if i.collide_point(touch.x, touch.y): - triggered = True - if touch_event == "down": - i.on_touch_down(touch) - elif touch_event == "move": - i.on_touch_move(touch, *args) - elif touch_event == "up": - i.on_touch_up(touch) - return triggered - - class OneLineListItem(BaseListItem): """A one line list item.""" _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() _num_lines = 1 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(48) if not self._height else self._height @@ -794,7 +1258,7 @@ class TwoLineListItem(BaseListItem): """A two line list item.""" _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() def __init__(self, **kwargs): @@ -806,73 +1270,75 @@ class ThreeLineListItem(BaseListItem): """A three line list item.""" _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() _num_lines = 3 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(88) if not self._height else self._height -class OneLineAvatarListItem(ContainerSupport, BaseListItem): +class OneLineAvatarListItem(BaseListItem): _txt_left_pad = NumericProperty("72dp") _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("19dp") # dp(24) - dp(5) + _txt_bot_pad = NumericProperty("19dp") _height = NumericProperty() _num_lines = 1 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(56) if not self._height else self._height class TwoLineAvatarListItem(OneLineAvatarListItem): _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() _num_lines = 2 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(72) if not self._height else self._height -class ThreeLineAvatarListItem(ContainerSupport, ThreeLineListItem): +class ThreeLineAvatarListItem(ThreeLineListItem): _txt_left_pad = NumericProperty("72dp") + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) -class OneLineIconListItem(ContainerSupport, OneLineListItem): + +class OneLineIconListItem(OneLineListItem): _txt_left_pad = NumericProperty("72dp") class TwoLineIconListItem(OneLineIconListItem): _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() _num_lines = 2 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.height = dp(72) if not self._height else self._height -class ThreeLineIconListItem(ContainerSupport, ThreeLineListItem): +class ThreeLineIconListItem(ThreeLineListItem): _txt_left_pad = NumericProperty("72dp") -class OneLineRightIconListItem(ContainerSupport, OneLineListItem): - # dp(40) = dp(16) + dp(24): +class OneLineRightIconListItem(OneLineListItem): _txt_right_pad = NumericProperty("40dp") - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS class TwoLineRightIconListItem(OneLineRightIconListItem): _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) + _txt_bot_pad = NumericProperty("15dp") _height = NumericProperty() _num_lines = 2 @@ -881,8 +1347,7 @@ class TwoLineRightIconListItem(OneLineRightIconListItem): self.height = dp(72) if not self._height else self._height -class ThreeLineRightIconListItem(ContainerSupport, ThreeLineListItem): - # dp(40) = dp(16) + dp(24): +class ThreeLineRightIconListItem(ThreeLineListItem): _txt_right_pad = NumericProperty("40dp") def __init__(self, **kwargs): @@ -891,29 +1356,26 @@ class ThreeLineRightIconListItem(ContainerSupport, ThreeLineListItem): class OneLineAvatarIconListItem(OneLineAvatarListItem): - # dp(40) = dp(16) + dp(24): _txt_right_pad = NumericProperty("40dp") - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS class TwoLineAvatarIconListItem(TwoLineAvatarListItem): - # dp(40) = dp(16) + dp(24): _txt_right_pad = NumericProperty("40dp") - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem): - # dp(40) = dp(16) + dp(24): _txt_right_pad = NumericProperty("40dp") - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS diff --git a/sbapp/kivymd/uix/menu/menu.kv b/sbapp/kivymd/uix/menu/menu.kv index d4ac30e..f72f87d 100644 --- a/sbapp/kivymd/uix/menu/menu.kv +++ b/sbapp/kivymd/uix/menu/menu.kv @@ -1,4 +1,4 @@ -#:import STD_INC kivymd.material_resources.STANDARD_INCREMENT +#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT @@ -14,7 +14,7 @@ size_hint: None, None - width: root.width_mult * STD_INC + width: root.width_mult * STANDARD_INCREMENT bar_width: 0 key_viewclass: "viewclass" key_size: "height" @@ -28,7 +28,7 @@ orientation: "vertical" - + diff --git a/sbapp/kivymd/uix/menu/menu.py b/sbapp/kivymd/uix/menu/menu.py index ebe1461..102cf96 100755 --- a/sbapp/kivymd/uix/menu/menu.py +++ b/sbapp/kivymd/uix/menu/menu.py @@ -781,7 +781,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout): and defaults to `'[dp(7)]'`. """ - elevation = NumericProperty(10) + elevation = NumericProperty(4) """ Elevation value of menu dialog. @@ -790,7 +790,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout): .. code-block:: python self.menu = MDDropdownMenu( - elevation=16, + elevation=4, ..., ) @@ -798,7 +798,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout): :align: center :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `10`. + and defaults to `4`. """ _start_coords = [] diff --git a/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py b/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py index 5dea20d..9ac5823 100755 --- a/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py +++ b/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py @@ -24,7 +24,7 @@ Anatomy MDNavigationLayout: - ScreenManager: + MDScreenManager: Screen_1: @@ -39,50 +39,108 @@ Anatomy A simple example ---------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV styles - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.app import MDApp + .. code-block:: python - KV = ''' - MDScreen: + from kivy.lang import Builder - MDNavigationLayout: + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.app import MDApp - ScreenManager: + KV = ''' + MDScreen: - MDScreen: + MDNavigationLayout: - MDTopAppBar: - title: "Navigation Drawer" - elevation: 10 - pos_hint: {"top": 1} - md_bg_color: "#e7e4c0" - specific_text_color: "#4a4939" - left_action_items: - [['menu', lambda x: nav_drawer.set_state("open")]] + MDScreenManager: + + MDScreen: + + MDTopAppBar: + title: "Navigation Drawer" + elevation: 4 + pos_hint: {"top": 1} + md_bg_color: "#e7e4c0" + specific_text_color: "#4a4939" + left_action_items: + [['menu', lambda x: nav_drawer.set_state("open")]] - MDNavigationDrawer: - id: nav_drawer - md_bg_color: "#f7f4e7" + MDNavigationDrawer: + id: nav_drawer + radius: (0, 16, 16, 0) - ContentNavigationDrawer: - ''' + ContentNavigationDrawer: + ''' - class ContentNavigationDrawer(MDBoxLayout): - pass + class ContentNavigationDrawer(MDBoxLayout): + pass - class TestNavigationDrawer(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - TestNavigationDrawer().run() + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager + from kivymd.uix.toolbar import MDTopAppBar + + + class ContentNavigationDrawer(MDBoxLayout): + pass + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return( + MDScreen( + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDTopAppBar( + title="Navigation Drawer", + elevation=4, + pos_hint={"top": 1}, + md_bg_color="#e7e4c0", + specific_text_color="#4a4939", + left_action_items=[ + ['menu', lambda x: self.nav_drawer_open()] + ], + ) + + ) + ), + MDNavigationDrawer( + ContentNavigationDrawer(), + id="nav_drawer", + radius=(0, 16, 16, 0), + ), + ), + ), + ) + + def nav_drawer_open(self, *args): + nav_drawer = self.root.children[0].ids.nav_drawer + nav_drawer.set_state("open") + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif :align: center @@ -90,211 +148,213 @@ A simple example .. Note:: :class:`~MDNavigationDrawer` is an empty :class:`~kivymd.uix.card.MDCard` panel. -Custom content for navigation drawer ------------------------------------- - -Let's extend the ``ContentNavigationDrawer`` class from the above example and -create content for our :class:`~MDNavigationDrawer` panel: - -.. code-block:: kv - - # Menu item in the DrawerList list. - - theme_text_color: "Custom" - on_release: self.parent.set_color_item(self) - - IconLeftWidget: - id: icon - icon: root.icon - theme_text_color: "Custom" - text_color: root.text_color - -.. code-block:: python - - class ItemDrawer(OneLineIconListItem): - icon = StringProperty() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-item.png - :align: center - -Top of ``ContentNavigationDrawer`` and ``DrawerList`` for menu items: - -.. code-block:: kv - - - orientation: "vertical" - padding: "8dp" - spacing: "8dp" - - AnchorLayout: - anchor_x: "left" - size_hint_y: None - height: avatar.height - - Image: - id: avatar - size_hint: None, None - size: "56dp", "56dp" - source: "kivymd.png" - - MDLabel: - text: "KivyMD library" - font_style: "Button" - size_hint_y: None - height: self.texture_size[1] - - MDLabel: - text: "kivydevelopment@gmail.com" - font_style: "Caption" - size_hint_y: None - height: self.texture_size[1] - - ScrollView: - - DrawerList: - id: md_list - -.. code-block:: python - - class ContentNavigationDrawer(BoxLayout): - pass - - - class DrawerList(ThemableBehavior, MDList): - def set_color_item(self, instance_item): - '''Called when tap on a menu item.''' - - # Set the color of the icon and text for the menu item. - for item in self.children: - if item.text_color == self.theme_cls.primary_color: - item.text_color = self.theme_cls.text_color - break - instance_item.text_color = self.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-top.png - :align: center - -Create a menu list for ``ContentNavigationDrawer``: - -.. code-block:: python - - def on_start(self): - icons_item = { - "folder": "My files", - "account-multiple": "Shared with me", - "star": "Starred", - "history": "Recent", - "checkbox-marked": "Shared with me", - "upload": "Upload", - } - for icon_name in icons_item.keys(): - self.root.ids.content_drawer.ids.md_list.add_widget( - ItemDrawer(icon=icon_name, text=icons_item[icon_name]) - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-work.gif - :align: center - Standard content for the navigation bar --------------------------------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV styles - from kivymd.app import MDApp + .. code-block:: python - KV = ''' - - focus_color: "#e7e4c0" - unfocus_color: "#f7f4e7" - text_color: "#4a4939" - icon_color: "#4a4939" - ripple_color: "#c5bdd2" - selected_color: "#0c6c4d" + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + + focus_color: "#e7e4c0" + text_color: "#4a4939" + icon_color: "#4a4939" + ripple_color: "#c5bdd2" + selected_color: "#0c6c4d" - - bg_color: "#f7f4e7" - text_color: "#4a4939" - icon_color: "#4a4939" - _no_ripple_effect: True + + text_color: "#4a4939" + icon_color: "#4a4939" + focus_behavior: False + selected_color: "#4a4939" + _no_ripple_effect: True - MDScreen: + MDScreen: - MDNavigationLayout: + MDNavigationLayout: - ScreenManager: + MDScreenManager: - MDScreen: + MDScreen: - MDTopAppBar: - title: "Navigation Drawer" - elevation: 10 - pos_hint: {"top": 1} - md_bg_color: "#e7e4c0" - specific_text_color: "#4a4939" - left_action_items: - [ \ - [ \ - 'menu', lambda x: \ - nav_drawer.set_state("open") \ - if nav_drawer.state == "close" else \ - nav_drawer.set_state("close") \ - ] \ - ] + MDTopAppBar: + title: "Navigation Drawer" + elevation: 4 + pos_hint: {"top": 1} + md_bg_color: "#e7e4c0" + specific_text_color: "#4a4939" + left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] - MDNavigationDrawer: - id: nav_drawer - radius: (0, 16, 16, 0) if self.anchor == "left" else (16, 0, 0, 16) - md_bg_color: "#f7f4e7" + MDNavigationDrawer: + id: nav_drawer + radius: (0, 16, 16, 0) - MDNavigationDrawerMenu: + MDNavigationDrawerMenu: - MDNavigationDrawerHeader: - title: "Header title" - title_color: "#4a4939" - text: "Header text" - title_color: "#4a4939" - spacing: "4dp" - padding: "12dp", 0, 0, "56dp" + MDNavigationDrawerHeader: + title: "Header title" + title_color: "#4a4939" + text: "Header text" + spacing: "4dp" + padding: "12dp", 0, 0, "56dp" - MDNavigationDrawerLabel: - text: "Mail" + MDNavigationDrawerLabel: + text: "Mail" - DrawerClickableItem: - icon: "gmail" - right_text: "+99" - text_right_color: "#4a4939" - text: "Inbox" + DrawerClickableItem: + icon: "gmail" + right_text: "+99" + text_right_color: "#4a4939" + text: "Inbox" - DrawerClickableItem: - icon: "send" - text: "Outbox" + DrawerClickableItem: + icon: "send" + text: "Outbox" - MDNavigationDrawerDivider: + MDNavigationDrawerDivider: - MDNavigationDrawerLabel: - text: "Labels" + MDNavigationDrawerLabel: + text: "Labels" - DrawerLabelItem: - icon: "information-outline" - text: "Label" + DrawerLabelItem: + icon: "information-outline" + text: "Label" - DrawerLabelItem: - icon: "information-outline" - text: "Label" - ''' + DrawerLabelItem: + icon: "information-outline" + text: "Label" + ''' - class TestNavigationDrawer(MDApp): - def build(self): - self.theme_cls.primary_palette = "Indigo" - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) - TestNavigationDrawer().run() + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.navigationdrawer import ( + MDNavigationLayout, + MDNavigationDrawer, + MDNavigationDrawerMenu, + MDNavigationDrawerHeader, + MDNavigationDrawerLabel, + MDNavigationDrawerDivider, + MDNavigationDrawerItem, + ) + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager + from kivymd.uix.toolbar import MDTopAppBar + + + class BaseNavigationDrawerItem(MDNavigationDrawerItem): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.radius = 24 + self.text_color = "#4a4939" + self.icon_color = "#4a4939" + self.focus_color = "#e7e4c0" + + + class DrawerLabelItem(BaseNavigationDrawerItem): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.focus_behavior = False + self._no_ripple_effect = True + self.selected_color = "#4a4939" + + + class DrawerClickableItem(BaseNavigationDrawerItem): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.ripple_color = "#c5bdd2" + self.selected_color = "#0c6c4d" + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return( + MDScreen( + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDTopAppBar( + title="Navigation Drawer", + elevation=4, + pos_hint={"top": 1}, + md_bg_color="#e7e4c0", + specific_text_color="#4a4939", + left_action_items=[ + ['menu', lambda x: self.nav_drawer_open()] + ], + ) + + ) + ), + MDNavigationDrawer( + MDNavigationDrawerMenu( + MDNavigationDrawerHeader( + title="Header title", + title_color="#4a4939", + text="Header text", + spacing="4dp", + padding=("12dp", 0, 0, "56dp"), + ), + MDNavigationDrawerLabel( + text="Mail", + ), + DrawerClickableItem( + icon="gmail", + right_text="+99", + text_right_color="#4a4939", + text="Inbox", + ), + DrawerClickableItem( + icon="send", + text="Outbox", + ), + MDNavigationDrawerDivider(), + MDNavigationDrawerLabel( + text="Labels", + ), + DrawerLabelItem( + icon="information-outline", + text="Label", + ), + DrawerLabelItem( + icon="information-outline", + text="Label", + ), + ), + id="nav_drawer", + radius=(0, 16, 16, 0), + ) + ) + ) + ) + + def nav_drawer_open(self, *args): + nav_drawer = self.root.children[0].ids.nav_drawer + nav_drawer.set_state("open") + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standatd-content.gif :align: center @@ -302,83 +362,164 @@ Standard content for the navigation bar Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar`` ----------------------------------------------------------------------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.properties import ObjectProperty + .. tab:: Declarative KV styles - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout + .. code-block:: python - KV = ''' - + from kivy.lang import Builder + from kivy.properties import ObjectProperty - ScrollView: + from kivymd.app import MDApp + from kivymd.uix.scrollview import MDScrollView - MDList: + KV = ''' + - OneLineListItem: - text: "Screen 1" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 1" + MDList: - OneLineListItem: - text: "Screen 2" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 2" - - - MDScreen: - - MDTopAppBar: - id: toolbar - pos_hint: {"top": 1} - elevation: 10 - title: "MDNavigationDrawer" - left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] - - MDNavigationLayout: - x: toolbar.height - - ScreenManager: - id: screen_manager - - MDScreen: - name: "scr 1" - - MDLabel: + OneLineListItem: text: "Screen 1" - halign: "center" + on_press: + root.nav_drawer.set_state("close") + root.screen_manager.current = "scr 1" - MDScreen: - name: "scr 2" - - MDLabel: + OneLineListItem: text: "Screen 2" - halign: "center" - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - screen_manager: screen_manager - nav_drawer: nav_drawer - ''' + on_press: + root.nav_drawer.set_state("close") + root.screen_manager.current = "scr 2" - class ContentNavigationDrawer(MDBoxLayout): - screen_manager = ObjectProperty() - nav_drawer = ObjectProperty() + MDScreen: + + MDTopAppBar: + pos_hint: {"top": 1} + elevation: 4 + title: "MDNavigationDrawer" + left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] + + MDNavigationLayout: + + MDScreenManager: + id: screen_manager + + MDScreen: + name: "scr 1" + + MDLabel: + text: "Screen 1" + halign: "center" + + MDScreen: + name: "scr 2" + + MDLabel: + text: "Screen 2" + halign: "center" + + MDNavigationDrawer: + id: nav_drawer + radius: (0, 16, 16, 0) + + ContentNavigationDrawer: + screen_manager: screen_manager + nav_drawer: nav_drawer + ''' - class TestNavigationDrawer(MDApp): - def build(self): - return Builder.load_string(KV) + class ContentNavigationDrawer(MDScrollView): + screen_manager = ObjectProperty() + nav_drawer = ObjectProperty() - TestNavigationDrawer().run() + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.label import MDLabel + from kivymd.uix.list import MDList, OneLineListItem + from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager + from kivymd.uix.scrollview import MDScrollView + from kivymd.uix.toolbar import MDTopAppBar + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Dark" + return ( + MDScreen( + MDTopAppBar( + pos_hint={"top": 1}, + elevation=4, + title="MDNavigationDrawer", + left_action_items=[["menu", lambda x: self.nav_drawer_open()]], + ), + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDLabel( + text="Screen 1", + halign="center", + ), + name="scr 1", + ), + MDScreen( + MDLabel( + text="Screen 2", + halign="center", + ), + name="scr 2", + ), + id="screen_manager", + ), + MDNavigationDrawer( + MDScrollView( + MDList( + OneLineListItem( + text="Screen 1", + on_press=self.switch_screen, + ), + OneLineListItem( + text="Screen 2", + on_press=self.switch_screen, + ), + ), + ), + id="nav_drawer", + radius=(0, 16, 16, 0), + ), + id="navigation_layout", + ) + ) + ) + + def switch_screen(self, instance_list_item: OneLineListItem): + self.root.ids.navigation_layout.ids.screen_manager.current = { + "Screen 1": "scr 1", "Screen 2": "scr 2" + }[instance_list_item.text] + self.root.children[0].ids.nav_drawer.set_state("close") + + def nav_drawer_open(self): + nav_drawer = self.root.children[0].ids.nav_drawer + nav_drawer.set_state("open") + + + Example().run() """ __all__ = ( @@ -410,15 +551,15 @@ from kivy.properties import ( StringProperty, VariableListProperty, ) -from kivy.uix.floatlayout import FloatLayout from kivy.uix.screenmanager import ScreenManager -from kivy.uix.scrollview import ScrollView from kivymd import uix_path -from kivymd.uix.behaviors import FakeRectangularElevationBehavior, FocusBehavior +from kivymd.uix.behaviors.focus_behavior import FocusBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.card import MDCard +from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.list import MDList, OneLineAvatarIconListItem +from kivymd.uix.scrollview import MDScrollView from kivymd.uix.toolbar import MDTopAppBar with open( @@ -432,14 +573,19 @@ class NavigationDrawerContentError(Exception): pass -class MDNavigationLayout(FloatLayout): +class MDNavigationLayout(MDFloatLayout): + """ + For more information, see in the :class:`~kivymd.uix.floatlayout.MDFloatLayout` + class documentation. + """ + _scrim_color = ObjectProperty(None) _scrim_rectangle = ObjectProperty(None) _screen_manager = ObjectProperty(None) _navigation_drawer = ObjectProperty(None) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.bind(width=self.update_pos) def update_pos(self, instance_navigation_drawer, pos_x: float) -> None: @@ -514,6 +660,9 @@ class MDNavigationDrawerLabel(MDBoxLayout): """ Implements a label for a menu for :class:`~MDNavigationDrawer` class. + For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` + class documentation. + .. versionadded:: 1.0.0 .. code-block:: kv @@ -554,6 +703,9 @@ class MDNavigationDrawerDivider(MDBoxLayout): """ Implements a divider for a menu for :class:`~MDNavigationDrawer` class. + For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` + class documentation. + .. versionadded:: 1.0.0 .. code-block:: kv @@ -596,6 +748,9 @@ class MDNavigationDrawerHeader(MDBoxLayout): """ Implements a header for a menu for :class:`~MDNavigationDrawer` class. + For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` + class documentation. + .. versionadded:: 1.0.0 .. code-block:: kv @@ -737,6 +892,9 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior): """ Implements an item for the :class:`~MDNavigationDrawer` menu list. + For more information, see in the + :class:`~kivymd.uix.list.OneLineAvatarIconListItem` class documentation. + .. versionadded:: 1.0.0 .. code-block:: kv @@ -814,11 +972,14 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior): _drawer_menu = ObjectProperty() -class MDNavigationDrawerMenu(ScrollView): +class MDNavigationDrawerMenu(MDScrollView): """ Implements a scrollable list for menu items of the :class:`~MDNavigationDrawer` class. + For more information, see in the + :class:`~kivymd.uix.scrollview.MDScrollView` class documentation. + .. versionadded:: 1.0.0 .. code-block:: kv @@ -863,7 +1024,7 @@ class MDNavigationDrawerMenu(ScrollView): widget.text_color = widget._text_color -class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): +class MDNavigationDrawer(MDCard): type = OptionProperty("modal", options=("standard", "modal")) """ Type of drawer. Modal type will be on top of screen. Standard type will be @@ -871,6 +1032,9 @@ class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing drawer for standard type. + For more information, see in the :class:`~kivymd.uix.card.MDCard` + class documentation. + Standard -------- @@ -882,7 +1046,7 @@ class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif :align: center - Model + Modal ----- .. code-block:: kv @@ -1121,8 +1285,8 @@ class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): and defaults to `0.2`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.bind( open_progress=self.update_status, status=self.update_status, diff --git a/sbapp/kivymd/uix/navigationrail/__init__.py b/sbapp/kivymd/uix/navigationrail/__init__.py index 0ec0480..7d3ff75 100644 --- a/sbapp/kivymd/uix/navigationrail/__init__.py +++ b/sbapp/kivymd/uix/navigationrail/__init__.py @@ -2,5 +2,6 @@ from .navigationrail import ( MDNavigationRail, MDNavigationRailFabButton, + MDNavigationRailItem, MDNavigationRailMenuButton, ) diff --git a/sbapp/kivymd/uix/navigationrail/navigationrail.kv b/sbapp/kivymd/uix/navigationrail/navigationrail.kv index 9baf444..e2623bb 100644 --- a/sbapp/kivymd/uix/navigationrail/navigationrail.kv +++ b/sbapp/kivymd/uix/navigationrail/navigationrail.kv @@ -25,7 +25,7 @@ orientation: "vertical" size_hint: None, None - size: self.navigation_rail.width, "56dp" + size: self.navigation_rail.width if self.navigation_rail else 100, "56dp" RelativeLayout: id: container @@ -43,7 +43,8 @@ opacity: 0 md_bg_color: root.navigation_rail.ripple_color_item \ - if root.navigation_rail.ripple_color_item else \ + if root.navigation_rail and \ + root.navigation_rail.ripple_color_item else \ app.theme_cls.primary_color MDIcon: @@ -66,7 +67,8 @@ text_color: ( \ root.navigation_rail.icon_color_item_normal \ - if root.navigation_rail.icon_color_item_normal else \ + if root.navigation_rail \ + and root.navigation_rail.icon_color_item_normal else \ app.theme_cls.text_color \ ) \ if not root.active else \ @@ -79,7 +81,8 @@ container.height - \ ( \ (self.height + dp(4)) \ - if root.navigation_rail.type == "unselected" else \ + if root.navigation_rail and \ + root.navigation_rail.type == "unselected" else \ (self.height - dp(8)) \ ) @@ -102,17 +105,20 @@ RoundedRectangle: radius: [root._selected_region_width / 2,] \ - if root.navigation_rail.type == "unselected" else \ + if root.navigation_rail and \ + root.navigation_rail.type == "unselected" else \ [root._selected_region_width / 4,] size: root._selected_region_width, \ root._selected_region_width \ - if root.navigation_rail.type == "unselected" else \ + if root.navigation_rail and \ + root.navigation_rail.type == "unselected" else \ root._selected_region_width / 2 pos: self.center_x - self.width - dp(4), \ self.center_y - root._selected_region_width / 2 \ - if root.navigation_rail.type == "unselected" else \ + if root.navigation_rail and \ + root.navigation_rail.type == "unselected" else \ self.center_y - root._selected_region_width / 4 MDLabel: @@ -125,12 +131,16 @@ pos_hint: {"center_x": .5} y: "16" font_style: "Body2" - font_name: root.navigation_rail.font_name theme_text_color: "Custom" + font_name: + root.navigation_rail.font_name \ + if root.navigation_rail else \ + "Roboto" text_color: ( \ root.navigation_rail.text_color_item_normal \ - if root.navigation_rail.text_color_item_normal else \ + if root.navigation_rail and \ + root.navigation_rail.text_color_item_normal else \ app.theme_cls.text_color \ ) \ if not root.active else \ @@ -140,6 +150,8 @@ app.theme_cls.text_color \ ) opacity: - (0 if root.navigation_rail.type == "unselected" else 1) \ - if root.navigation_rail.type != "selected" else \ + (0 if root.navigation_rail and \ + root.navigation_rail.type == "unselected" else 1) \ + if root.navigation_rail and \ + root.navigation_rail.type != "selected" else \ (0 if not root.active else 1) diff --git a/sbapp/kivymd/uix/navigationrail/navigationrail.py b/sbapp/kivymd/uix/navigationrail/navigationrail.py index 881d6cd..42e2e24 100644 --- a/sbapp/kivymd/uix/navigationrail/navigationrail.py +++ b/sbapp/kivymd/uix/navigationrail/navigationrail.py @@ -29,45 +29,86 @@ Usage MDNavigationRailItem: -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp + .. code-block:: python - KV = ''' + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDBoxLayout: + + MDNavigationRail: + + MDNavigationRailItem: + text: "Python" + icon: "language-python" + + MDNavigationRailItem: + text: "JavaScript" + icon: "language-javascript" + + MDNavigationRailItem: + text: "CPP" + icon: "language-cpp" + + MDNavigationRailItem: + text: "Git" + icon: "git" + + MDScreen: + ''' - MDBoxLayout: - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - - MDNavigationRailItem: - text: "JavaScript" - icon: "language-javascript" - - MDNavigationRailItem: - text: "CPP" - icon: "language-cpp" - - MDNavigationRailItem: - text: "Git" - icon: "git" - - MDScreen: - ''' + class Example(MDApp): + def build(self): + return Builder.load_string(KV) - class Example(MDApp): - def build(self): - return Builder.load_string(KV) + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem - Example().run() + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDNavigationRail( + MDNavigationRailItem( + text="Python", + icon="language-python", + ), + MDNavigationRailItem( + text="JavaScript", + icon="language-javascript", + ), + MDNavigationRailItem( + text="CPP", + icon="language-cpp", + ), + MDNavigationRailItem( + text="Git", + icon="git", + ), + ) + ) + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png :align: center @@ -75,202 +116,412 @@ Usage Example ======= -.. code-block:: python +.. tabs:: - from kivy.clock import Clock - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.behaviors import RoundedRectangularElevationBehavior - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDFillRoundFlatIconButton - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen + .. code-block:: python - KV = ''' - #:import FadeTransition kivy.uix.screenmanager.FadeTransition + from kivy.clock import Clock + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CommonElevationBehavior + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDFillRoundFlatIconButton + from kivymd.uix.label import MDLabel + from kivymd.uix.screen import MDScreen + + KV = ''' + #:import FadeTransition kivy.uix.screenmanager.FadeTransition - - elevation: 3 - -height: "56dp" + + elevation: 3.5 + shadow_radius: 12 + shadow_softness: 4 + -height: "56dp" - - focus_color: "#e7e4c0" - unfocus_color: "#fffcf4" + + focus_color: "#e7e4c0" + unfocus_color: "#fffcf4" - MDScreen: + MDScreen: - MDNavigationLayout: + MDNavigationLayout: - ScreenManager: + ScreenManager: - MDScreen: + MDScreen: - MDBoxLayout: - orientation: "vertical" + MDBoxLayout: + orientation: "vertical" + + MDBoxLayout: + adaptive_height: True + md_bg_color: "#fffcf4" + padding: "12dp" + + MDLabel: + text: "12:00" + adaptive_height: True + pos_hint: {"center_y": .5} + + MDBoxLayout: + + MDNavigationRail: + id: navigation_rail + md_bg_color: "#fffcf4" + selected_color_background: "#e7e4c0" + ripple_color_item: "#e7e4c0" + on_item_release: app.switch_screen(*args) + + MDNavigationRailMenuButton: + on_release: nav_drawer.set_state("open") + + MDNavigationRailFabButton: + md_bg_color: "#b0f0d6" + + MDNavigationRailItem: + text: "Python" + icon: "language-python" + + MDNavigationRailItem: + text: "JavaScript" + icon: "language-javascript" + + MDNavigationRailItem: + text: "CPP" + icon: "language-cpp" + + MDNavigationRailItem: + text: "Swift" + icon: "language-swift" + + ScreenManager: + id: screen_manager + transition: + FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark) + + MDNavigationDrawer: + id: nav_drawer + radius: (0, 16, 16, 0) + md_bg_color: "#fffcf4" + elevation: 4 + width: "240dp" + + MDNavigationDrawerMenu: MDBoxLayout: + orientation: "vertical" adaptive_height: True - md_bg_color: "#fffcf4" - padding: "12dp" + spacing: "12dp" + padding: "3dp", 0, 0, "12dp" - MDLabel: - text: "12:00" - adaptive_height: True - pos_hint: {"center_y": .5} + MDIconButton: + icon: "menu" - MDBoxLayout: + ExtendedButton: + text: "Compose" + icon: "pencil" - MDNavigationRail: - id: navigation_rail - md_bg_color: "#fffcf4" - selected_color_background: "#e7e4c0" - ripple_color_item: "#e7e4c0" - on_item_release: app.switch_screen(*args) + DrawerClickableItem: + text: "Python" + icon: "language-python" - MDNavigationRailMenuButton: - on_release: nav_drawer.set_state("open") + DrawerClickableItem: + text: "JavaScript" + icon: "language-javascript" - MDNavigationRailFabButton: - md_bg_color: "#b0f0d6" + DrawerClickableItem: + text: "CPP" + icon: "language-cpp" - MDNavigationRailItem: - text: "Python" - icon: "language-python" - - MDNavigationRailItem: - text: "JavaScript" - icon: "language-javascript" - - MDNavigationRailItem: - text: "CPP" - icon: "language-cpp" - - MDNavigationRailItem: - text: "Swift" - icon: "language-swift" - - ScreenManager: - id: screen_manager - transition: - FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark) - - MDNavigationDrawer: - id: nav_drawer - radius: (0, 16, 16, 0) - md_bg_color: "#fffcf4" - elevation: 12 - width: "240dp" - - MDNavigationDrawerMenu: - - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - spacing: "12dp" - padding: 0, 0, 0, "12dp" - - MDIconButton: - icon: "menu" - - ExtendedButton: - text: "Compose" - icon: "pencil" - - DrawerClickableItem: - text: "Python" - icon: "language-python" - - DrawerClickableItem: - text: "JavaScript" - icon: "language-javascript" - - DrawerClickableItem: - text: "CPP" - icon: "language-cpp" - - DrawerClickableItem: - text: "Swift" - icon: "language-swift" - ''' - - - class ExtendedButton( - RoundedRectangularElevationBehavior, MDFillRoundFlatIconButton - ): - ''' - Implements a button of type - `Extended FAB `_. - - .. rubric:: - Extended FABs help people take primary actions. - They're wider than FABs to accommodate a text label and larger target - area. - - This type of buttons is not yet implemented in the standard widget set - of the KivyMD library, so we will implement it ourselves in this class. - ''' - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.padding = "16dp" - Clock.schedule_once(self.set_spacing) - - def set_spacing(self, interval): - self.ids.box.spacing = "12dp" - - def set_radius(self, *args): - if self.rounded_button: - self._radius = self.radius = self.height / 4 - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def switch_screen( - self, instance_navigation_rail, instance_navigation_rail_item - ): - ''' - Called when tapping on rail menu items. Switches application screens. + DrawerClickableItem: + text: "Swift" + icon: "language-swift" ''' - self.root.ids.screen_manager.current = ( - instance_navigation_rail_item.icon.split("-")[1].lower() + + class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior): + ''' + Implements a button of type + `Extended FAB `_. + + .. rubric:: + Extended FABs help people take primary actions. + They're wider than FABs to accommodate a text label and larger target + area. + + This type of buttons is not yet implemented in the standard widget set + of the KivyMD library, so we will implement it ourselves in this class. + ''' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.padding = "16dp" + Clock.schedule_once(self.set_spacing) + + def set_spacing(self, interval): + self.ids.box.spacing = "12dp" + + def set_radius(self, *args): + if self.rounded_button: + self._radius = self.radius = self.height / 4 + + + class Example(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + def switch_screen( + self, instance_navigation_rail, instance_navigation_rail_item + ): + ''' + Called when tapping on rail menu items. Switches application screens. + ''' + + self.root.ids.screen_manager.current = ( + instance_navigation_rail_item.icon.split("-")[1].lower() + ) + + def on_start(self): + '''Creates application screens.''' + + navigation_rail_items = self.root.ids.navigation_rail.get_items()[:] + navigation_rail_items.reverse() + + for widget in navigation_rail_items: + name_screen = widget.icon.split("-")[1].lower() + screen = MDScreen( + name=name_screen, + md_bg_color="#edd769", + radius=[18, 0, 0, 0], + ) + box = MDBoxLayout(padding="12dp") + label = MDLabel( + text=name_screen.capitalize(), + font_style="H1", + halign="right", + adaptive_height=True, + shorten=True, + ) + box.add_widget(label) + screen.add_widget(box) + self.root.ids.screen_manager.add_widget(screen) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.clock import Clock + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.uix.behaviors import CommonElevationBehavior + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDFillRoundFlatIconButton, MDIconButton + from kivymd.uix.label import MDLabel + from kivymd.uix.navigationdrawer import ( + MDNavigationDrawerItem, + MDNavigationLayout, + MDNavigationDrawer, + MDNavigationDrawerMenu, ) - - def on_start(self): - '''Creates application screens.''' - - navigation_rail_items = self.root.ids.navigation_rail.get_items()[:] - navigation_rail_items.reverse() - - for widget in navigation_rail_items: - name_screen = widget.icon.split("-")[1].lower() - screen = MDScreen( - name=name_screen, - md_bg_color="#edd769", - radius=[18, 0, 0, 0], - ) - box = MDBoxLayout(padding="12dp") - label = MDLabel( - text=name_screen.capitalize(), - font_style="H1", - halign="right", - adaptive_height=True, - shorten=True, - ) - box.add_widget(label) - screen.add_widget(box) - self.root.ids.screen_manager.add_widget(screen) + from kivymd.uix.navigationrail import ( + MDNavigationRail, + MDNavigationRailMenuButton, + MDNavigationRailFabButton, + MDNavigationRailItem, + ) + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager - Example().run() + class DrawerClickableItem(MDNavigationDrawerItem): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.focus_color = "#e7e4c0" + self.unfocus_color = self.theme_cls.bg_light + self.radius = 24 + + + class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.padding = "16dp" + self.elevation = 3.5 + self.shadow_radius = 12 + self.shadow_softness = 4 + self.height = dp(56) + Clock.schedule_once(self.set_spacing) + + def set_spacing(self, interval): + self.ids.box.spacing = "12dp" + + def set_radius(self, *args): + if self.rounded_button: + self._radius = self.radius = self.height / 4 + + + class Example(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + self.theme_cls.primary_palette = "Orange" + return MDScreen( + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDBoxLayout( + MDBoxLayout( + MDLabel( + text="12:00", + adaptive_height=True, + pos_hint={"center_y": 0.5}, + ), + adaptive_height=True, + md_bg_color="#fffcf4", + padding="12dp", + ), + MDBoxLayout( + MDNavigationRail( + MDNavigationRailMenuButton( + on_release=self.open_nav_drawer, + ), + MDNavigationRailFabButton( + md_bg_color="#b0f0d6", + ), + MDNavigationRailItem( + text="Python", + icon="language-python", + ), + MDNavigationRailItem( + text="JavaScript", + icon="language-javascript", + ), + MDNavigationRailItem( + text="CPP", + icon="language-cpp", + ), + MDNavigationRailItem( + text="Swift", + icon="language-swift", + ), + id="navigation_rail", + md_bg_color="#fffcf4", + selected_color_background="#e7e4c0", + ripple_color_item="#e7e4c0", + ), + MDScreenManager( + id="screen_manager_content", + ), + id="root_box", + ), + id="box_rail", + orientation="vertical", + ), + id="box", + ), + id="screen", + ), + id="screen_manager", + ), + MDNavigationDrawer( + MDNavigationDrawerMenu( + MDBoxLayout( + MDIconButton( + icon="menu", + ), + ExtendedButton( + text="Compose", + icon="pencil", + ), + orientation="vertical", + adaptive_height=True, + spacing="12dp", + padding=("3dp", 0, 0, "12dp"), + ), + DrawerClickableItem( + text="Python", + icon="language-python", + ), + DrawerClickableItem( + text="JavaScript", + icon="language-javascript", + ), + DrawerClickableItem( + text="CPP", + icon="language-cpp", + ), + DrawerClickableItem( + text="Swift", + icon="language-swift", + ), + ), + id="nav_drawer", + radius=(0, 16, 16, 0), + elevation=4, + width="240dp", + ), + ) + + def switch_screen(self, *args, screen_manager_content=None): + ''' + Called when tapping on rail menu items. Switches application screens. + ''' + + instance_navigation_rail, instance_navigation_rail_item = args + screen_manager_content.current = ( + instance_navigation_rail_item.icon.split("-")[1].lower() + ) + + def open_nav_drawer(self, *args): + self.root.ids.nav_drawer.set_state("open") + + def on_start(self): + '''Creates application screens.''' + + screen_manager = self.root.ids.screen_manager + root_box = screen_manager.ids.screen.ids.box.ids.box_rail.ids.root_box + navigation_rail = root_box.ids.navigation_rail + screen_manager_content = root_box.ids.screen_manager_content + navigation_rail_items = navigation_rail.get_items()[:] + navigation_rail_items.reverse() + navigation_rail.bind( + on_item_release=lambda *args: self.switch_screen( + *args, screen_manager_content=screen_manager_content + ) + ) + + for widget in navigation_rail_items: + name_screen = widget.icon.split("-")[1].lower() + screen_manager_content.add_widget( + MDScreen( + MDBoxLayout( + MDLabel( + text=name_screen.capitalize(), + font_style="H1", + halign="right", + adaptive_height=True, + shorten=True, + ), + padding="12dp", + ), + name=name_screen, + md_bg_color="#edd769", + radius=[18, 0, 0, 0], + ), + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif :align: center @@ -279,6 +530,7 @@ Example __all__ = ( "MDNavigationRail", + "MDNavigationRailItem", "MDNavigationRailFabButton", "MDNavigationRailMenuButton", ) @@ -305,12 +557,11 @@ from kivy.uix.behaviors import ButtonBehavior from kivymd import uix_path from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior +from kivymd.uix.behaviors import ScaleBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.button import MDFloatingActionButton, MDIconButton from kivymd.uix.card import MDCard from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.templates import ScaleWidget from kivymd.uix.widget import MDWidget with open( @@ -332,7 +583,7 @@ class PanelItems(MDBoxLayout): """Box for menu items.""" -class RippleWidget(MDWidget, ScaleWidget): +class RippleWidget(MDWidget, ScaleBehavior): """ Implements a background color for a menu item - (:class:`~MDNavigationRailItem`). @@ -561,7 +812,7 @@ class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout): self.navigation_rail.dispatch("on_item_release", self) -class MDNavigationRail(MDCard, FakeRectangularElevationBehavior): +class MDNavigationRail(MDCard): """ :Events: :attr:`on_item_press` @@ -852,8 +1103,8 @@ class MDNavigationRail(MDCard, FakeRectangularElevationBehavior): and defaults to `'Roboto'`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) Clock.schedule_once(self.set_pos_menu_fab_buttons) Clock.schedule_once(self.set_current_selected_item) self.register_event_type("on_item_press") diff --git a/sbapp/kivymd/uix/pickers/datepicker/datepicker.py b/sbapp/kivymd/uix/pickers/datepicker/datepicker.py index 50e80de..053afc8 100644 --- a/sbapp/kivymd/uix/pickers/datepicker/datepicker.py +++ b/sbapp/kivymd/uix/pickers/datepicker/datepicker.py @@ -11,65 +11,110 @@ Components/DatePicker .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png :align: center -.. warning:: The widget is under testing. Therefore, we would be grateful if - you would let us know about the bugs found. - .. rubric:: Usage -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.pickers import MDDatePicker + .. code-block:: python - KV = ''' - MDFloatLayout: + from kivy.lang import Builder - MDTopAppBar: - title: "MDDatePicker" - pos_hint: {"top": 1} - elevation: 10 + from kivymd.app import MDApp + from kivymd.uix.pickers import MDDatePicker - MDRaisedButton: - text: "Open date picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_date_picker() - ''' + KV = ''' + MDFloatLayout: - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_save(self, instance, value, date_range): - ''' - Events called when the "OK" dialog box button is clicked. - - :type instance: ; - - :param value: selected date; - :type value: ; - - :param date_range: list of 'datetime.date' objects in the selected range; - :type date_range: ; + MDRaisedButton: + text: "Open date picker" + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_date_picker() ''' - print(instance, value, date_range) - def on_cancel(self, instance, value): - '''Events called when the "CANCEL" dialog box button is clicked.''' + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def show_date_picker(self): - date_dialog = MDDatePicker() - date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) - date_dialog.open() + def on_save(self, instance, value, date_range): + ''' + Events called when the "OK" dialog box button is clicked. + + :type instance: ; + :param value: selected date; + :type value: ; + :param date_range: list of 'datetime.date' objects in the selected range; + :type date_range: ; + ''' + + print(instance, value, date_range) + + def on_cancel(self, instance, value): + '''Events called when the "CANCEL" dialog box button is clicked.''' + + def show_date_picker(self): + date_dialog = MDDatePicker() + date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) + date_dialog.open() - Test().run() + Test().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRaisedButton + from kivymd.uix.pickers import MDDatePicker + from kivymd.uix.screen import MDScreen -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.gif + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDRaisedButton( + text="Open data picker", + pos_hint={'center_x': .5, 'center_y': .5}, + on_release=self.show_date_picker, + ) + ) + ) + + def on_save(self, instance, value, date_range): + ''' + Events called when the "OK" dialog box button is clicked. + + :type instance: ; + + :param value: selected date; + :type value: ; + + :param date_range: list of 'datetime.date' objects in the selected range; + :type date_range: ; + ''' + + print(instance, value, date_range) + + def on_cancel(self, instance, value): + '''Events called when the "CANCEL" dialog box button is clicked.''' + + def show_date_picker(self, *args): + date_dialog = MDDatePicker() + date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) + date_dialog.open() + + + Test().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.png :align: center Open date dialog with the specified date @@ -81,7 +126,7 @@ Open date dialog with the specified date date_dialog = MDDatePicker(year=1983, month=4, day=12) date_dialog.open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/previous-date.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/specified-date.png :align: center Interval date @@ -94,12 +139,16 @@ that are not included in this range will have the status `disabled`. def show_date_picker(self): date_dialog = MDDatePicker( - min_date=datetime.date(2021, 2, 15), - max_date=datetime.date(2021, 3, 27), + min_date=datetime.date.today(), + max_date=datetime.date( + datetime.date.today().year, + datetime.date.today().month, + datetime.date.today().day + 2, + ), ) date_dialog.open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.png :align: center The range of available dates can be changed in the picker dialog: @@ -122,7 +171,7 @@ You can set the range of years using the :attr:`~kivymd.uix.picker.MDDatePicker. .. code-block:: python def show_date_picker(self): - date_dialog = MDDatePicker(min_year=2021, max_year=2030) + date_dialog = MDDatePicker(min_year=2022, max_year=2030) date_dialog.open() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png @@ -141,6 +190,8 @@ Set and select a date range :align: center """ +from __future__ import annotations + __all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField") import calendar @@ -152,7 +203,6 @@ from typing import Union from kivy import Logger from kivy.animation import Animation -from kivy.clock import Clock from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( @@ -175,7 +225,7 @@ from kivymd.theming import ThemableBehavior, ThemeManager from kivymd.toast import toast from kivymd.uix.behaviors import ( CircularRippleBehavior, - FakeRectangularElevationBehavior, + CommonElevationBehavior, SpecificBackgroundColorBehavior, ) from kivymd.uix.boxlayout import MDBoxLayout @@ -194,7 +244,7 @@ with open( class BaseDialogPicker( BaseDialog, - FakeRectangularElevationBehavior, + CommonElevationBehavior, SpecificBackgroundColorBehavior, ): """ @@ -255,11 +305,11 @@ class BaseDialogPicker( primary_color = ColorProperty(None) """ - Background color of toolbar in (r, g, b, a) format. + Background color of toolbar in (r, g, b, a) or string format. .. code-block:: python - MDDatePicker(primary_color=get_color_from_hex("#72225b")) + MDDatePicker(primary_color="brown") .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png :align: center @@ -270,13 +320,13 @@ class BaseDialogPicker( accent_color = ColorProperty(None) """ - Background color of calendar/clock face in (r, g, b, a) format. + Background color of calendar/clock face in (r, g, b, a) or string format. .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), + primary_color="brown", + accent_color="darkred", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png @@ -288,14 +338,15 @@ class BaseDialogPicker( selector_color = ColorProperty(None) """ - Background color of the selected day of the month or hour in (r, g, b, a) format. + Background color of the selected day of the month or hour in (r, g, b, a) + or string format. .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), + primary_color="brown", + accent_color="darkred", + selector_color="red", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png @@ -307,15 +358,15 @@ class BaseDialogPicker( text_toolbar_color = ColorProperty(None) """ - Color of labels for text on a toolbar in (r, g, b, a) format. + Color of labels for text on a toolbar in (r, g, b, a) or string format. .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png @@ -327,16 +378,16 @@ class BaseDialogPicker( text_color = ColorProperty(None) """ - Color of text labels in calendar/clock face in (r, g, b, a) format. + Color of text labels in calendar/clock face in (r, g, b, a) or string format. .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png @@ -348,17 +399,18 @@ class BaseDialogPicker( text_current_color = ColorProperty(None) """ - Color of the text of the current day of the month/hour in (r, g, b, a) format. + Color of the text of the current day of the month/hour in (r, g, b, a) + or string format. .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png @@ -375,13 +427,13 @@ class BaseDialogPicker( .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - text_button_color=(1, 1, 1, .5), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png @@ -391,52 +443,124 @@ class BaseDialogPicker( and defaults to `None`. """ - input_field_background_color = ColorProperty(None) + input_field_background_color_normal = ColorProperty(None) """ - Background color of input fields in (r, g, b, a) format. + Background color normal of input fields in (r, g, b, a) or string format. + + .. versionadded:: 1.1.0 .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", + input_field_background_color_normal="coral", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png :align: center - :attr:`input_field_background_color` is an :class:`~kivy.properties.ColorProperty` + :attr:`input_field_background_color_normal` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ + input_field_background_color_focus = ColorProperty(None) + """ + Background color normal of input fields in (r, g, b, a) or string format. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDDatePicker( + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", + input_field_background_color_normal="coral", + input_field_background_color_focus="red", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-focus-date.png + :align: center + + :attr:`input_field_background_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + input_field_background_color = ColorProperty(None) + """ + .. deprecated:: 1.1.0 + Use :attr:`input_field_background_color_normal` instead. + """ + input_field_text_color = ColorProperty(None) """ - Text color of input fields in (r, g, b, a) format. + .. deprecated:: 1.1.0 + Use :attr:`input_field_text_color_normal` instead. + """ - Background color of input fields. + input_field_text_color_normal = ColorProperty(None) + """ + Text color normal of input fields in (r, g, b, a) or string format. + + .. versionadded:: 1.1.0 .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), - input_field_text_color=(1, 1, 1, 1), + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", + input_field_background_color_normal="brown", + input_field_background_color_focus="red", + input_field_text_color_normal="white", ) - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png :align: center - :attr:`input_field_text_color` is an :class:`~kivy.properties.ColorProperty` + :attr:`input_field_text_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + input_field_text_color_focus = ColorProperty(None) + """ + Text color focus of input fields in (r, g, b, a) or string format. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDDatePicker( + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", + input_field_background_color_normal="brown", + input_field_background_color_focus="red", + input_field_text_color_normal="white", + ) + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png + :align: center + + :attr:`input_field_text_color_focus` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ @@ -447,16 +571,18 @@ class BaseDialogPicker( .. code-block:: python MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), - input_field_text_color=(1, 1, 1, 1), - font_name="Weather.ttf", - + primary_color="brown", + accent_color="darkred", + selector_color="red", + text_toolbar_color="lightgrey", + text_color="orange", + text_current_color="white", + text_button_color="lightgrey", + input_field_background_color_normal="brown", + input_field_background_color_focus="red", + input_field_text_color_normal="white", + input_field_text_color_focus="lightgrey", + font_name="nasalization.ttf", ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png @@ -471,6 +597,20 @@ class BaseDialogPicker( self.register_event_type("on_save") self.register_event_type("on_cancel") + def on_input_field_background_color( + self, instance, value: str | list | tuple + ) -> None: + """For supported of current API.""" + + self.input_field_background_color_normal = value + + def on_input_field_text_color( + self, instance, value: str | list | tuple + ) -> None: + """For supported of current API.""" + + self.input_field_text_color_normal = value + def on_save(self, *args) -> None: """Events called when the "OK" dialog box button is clicked.""" @@ -606,6 +746,13 @@ class DatePickerDaySelectableItem( self.owner.set_selected_widget(self) + def on_touch_down(self, touch): + # If year_layout is active don't dispatch on_touch_down events, + # so date items don't consume touch. + if not self.owner.ids._year_layout.disabled: + return + super().on_touch_down(touch) + class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): """Implements an item for a pick list of the year.""" @@ -661,7 +808,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): class MDDatePicker(BaseDialogPicker): text_weekday_color = ColorProperty(None) """ - Text color of weekday names in (r, g, b, a) format. + Text color of weekday names in (r, g, b, a) or string format. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-text-weekday-color.png :align: center @@ -1200,19 +1347,39 @@ class MDDatePicker(BaseDialogPicker): """Creates and returns a text field object used to enter dates.""" if issubclass(self.input_field_cls, MDTextField): + text_color_focus = ( + self.input_field_text_color_focus + if self.input_field_text_color_focus + else self.theme_cls.primary_color + ) + text_color_normal = ( + self.input_field_text_color_normal + if self.input_field_text_color_normal + else self.theme_cls.disabled_hint_text_color + ) + fill_color_focus = ( + self.input_field_background_color_focus + if self.input_field_background_color_focus + else self.theme_cls.bg_dark + ) + fill_color_normal = ( + self.input_field_background_color_normal + if self.input_field_background_color_normal + else self.theme_cls.bg_darkest + ) + field = self.input_field_cls( owner=self, helper_text=self.helper_text, - line_color_normal=self.theme_cls.divider_color, + fill_color_normal=fill_color_normal, + fill_color_focus=fill_color_focus, + hint_text_color_normal=text_color_normal, + hint_text_color_focus=text_color_focus, + text_color_normal=text_color_normal, + text_color_focus=text_color_focus, + line_color_focus=text_color_focus, + line_color_normal=text_color_normal, ) - field.color_mode = "custom" - field.line_color_focus = ( - self.theme_cls.primary_color - if not self.input_field_text_color - else self.input_field_text_color - ) - field.current_hint_text_color = field.line_color_focus - field._current_hint_text_color = field.line_color_focus return field else: raise TypeError( diff --git a/sbapp/kivymd/uix/pickers/timepicker/timepicker.py b/sbapp/kivymd/uix/pickers/timepicker/timepicker.py index 748637e..2b646d8 100644 --- a/sbapp/kivymd/uix/pickers/timepicker/timepicker.py +++ b/sbapp/kivymd/uix/pickers/timepicker/timepicker.py @@ -16,35 +16,73 @@ Components/TimePicker .. rubric:: Usage -.. code-block:: +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp - from kivymd.uix.pickers import MDTimePicker + .. code-block:: python - KV = ''' - MDFloatLayout: + from kivy.lang import Builder - MDRaisedButton: - text: "Open time picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_time_picker() - ''' + from kivymd.app import MDApp + from kivymd.uix.pickers import MDTimePicker + + KV = ''' + MDFloatLayout: + + MDRaisedButton: + text: "Open time picker" + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_time_picker() + ''' - class Test(MDApp): - def build(self): - return Builder.load_string(KV) + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def show_time_picker(self): - '''Open time picker dialog.''' + def show_time_picker(self): + '''Open time picker dialog.''' - time_dialog = MDTimePicker() - time_dialog.open() + time_dialog = MDTimePicker() + time_dialog.open() - Test().run() + Test().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.button import MDRaisedButton + from kivymd.uix.pickers import MDTimePicker + from kivymd.uix.screen import MDScreen + + + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDRaisedButton( + text="Open time picker", + pos_hint={'center_x': .5, 'center_y': .5}, + on_release=self.show_time_picker, + ) + ) + ) + + def show_time_picker(self, *args): + '''Open time picker dialog.''' + + MDTimePicker().open() + + + Test().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png :align: center @@ -91,14 +129,14 @@ Use the :attr:`~MDTimePicker.set_time` method of the .. code-block:: python - time_dialog = MDTimePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - text_button_color=(1, 1, 1, 1), - ) + MDTimePicker( + primary_color="brown", + accent_color="red", + text_button_color="white", + ).open() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png - :align: center +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png + :align: center """ __all__ = ("MDTimePicker",) @@ -194,11 +232,11 @@ class TimeInputTextField(MDTextField): hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$" minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$" - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) Clock.schedule_once(self.set_text) self.register_event_type("on_select") - self.bind(text_color=self.setter("hint_text_color_normal")) + self.bind(text_color_focus=self.setter("hint_text_color_normal")) def validate_time(self, text) -> Union[None, re.Match]: reg = self.hour_regx if self.num_type == "hour" else self.minute_regx @@ -217,17 +255,20 @@ class TimeInputTextField(MDTextField): to somehow make them aligned. """ - if not self.text: - self.text = " " + def set_text(*args): + if not self.text: + self.text = " " - self._refresh_text(self.text) - max_size = max(self._lines_rects, key=lambda r: r.size[0]).size - dx = (self.width - max_size[0]) / 2.0 - dy = (self.height - max_size[1]) / 2.0 - self.padding = [dx, dy, dx, dy] + self._refresh_text(self.text) + max_size = max(self._lines_rects, key=lambda r: r.size[0]).size + dx = (self.width - max_size[0]) / 2.0 + dy = (self.height - max_size[1]) / 2.0 + self.padding = [dx, dy, dx, dy] - if len(self.text) > 1: - self.text = self.text.replace(" ", "") + if len(self.text) > 1: + self.text = self.text.replace(" ", "") + + Clock.schedule_once(set_text) def on_focus(self, *args) -> None: super().on_focus(*args) diff --git a/sbapp/kivymd/uix/progressbar/progressbar.py b/sbapp/kivymd/uix/progressbar/progressbar.py index e7baaf7..9cedee8 100644 --- a/sbapp/kivymd/uix/progressbar/progressbar.py +++ b/sbapp/kivymd/uix/progressbar/progressbar.py @@ -247,7 +247,7 @@ class MDProgressBar(ThemableBehavior, ProgressBar): Clock.schedule_once(self.check_size) def check_size(self, interval: Union[int, float]) -> None: - if self.size == [100, 100]: + if self.height == 100: if self.orientation == "horizontal": self.size_hint_y = None self.height = dp(4) diff --git a/sbapp/kivymd/uix/recyclegridlayout.py b/sbapp/kivymd/uix/recyclegridlayout.py index 8a45768..d3ed891 100644 --- a/sbapp/kivymd/uix/recyclegridlayout.py +++ b/sbapp/kivymd/uix/recyclegridlayout.py @@ -1,14 +1,14 @@ """ Components/RecycleGridLayout -===================== +============================ -:class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` class equivalent. Simplifies working -with some widget properties. For example: +:class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` class equivalent. +Simplifies working with some widget properties. For example: -GridLayout ----------- +RecycleGridLayout +----------------- -.. code-block:: +.. code-block:: kv RecycleGridLayout: size_hint_y: None @@ -22,9 +22,9 @@ GridLayout size: self.size MDRecycleGridLayout ------------- +------------------- -.. code-block:: +.. code-block:: kv MDRecycleGridLayout: adaptive_height: True @@ -86,7 +86,13 @@ Equivalent from kivy.uix.recyclegridlayout import RecycleGridLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDRecycleGridLayout(RecycleGridLayout, MDAdaptiveWidget): - pass +class MDRecycleGridLayout( + DeclarativeBehavior, RecycleGridLayout, MDAdaptiveWidget +): + """ + Recycle grid layout layout class. For more information, see in the + :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/recycleview.py b/sbapp/kivymd/uix/recycleview.py new file mode 100644 index 0000000..9792103 --- /dev/null +++ b/sbapp/kivymd/uix/recycleview.py @@ -0,0 +1,44 @@ +""" +Components/RecycleView +====================== + +.. versionadded:: 1.0.0 + +:class:`~kivy.uix.recycleview.RecycleView` class equivalent. Simplifies working +with some widget properties. For example: + +RecycleView +----------- + +.. code-block:: kv + + RecycleView: + + canvas: + Color: + rgba: app.theme_cls.primary_color + Rectangle: + pos: self.pos + size: self.size + +MDRecycleView +------------- + +.. code-block:: kv + + MDRecycleView: + md_bg_color: app.theme_cls.primary_color +""" + +__all__ = ("MDRecycleView",) + +from kivy.uix.recycleview import RecycleView + +from kivymd.uix.behaviors import DeclarativeBehavior + + +class MDRecycleView(DeclarativeBehavior, RecycleView): + """ + Recycle view class. For more information, see in the + :class:`~kivy.uix.recycleview.RecycleView` class documentation. + """ diff --git a/sbapp/kivymd/uix/refreshlayout/refreshlayout.py b/sbapp/kivymd/uix/refreshlayout/refreshlayout.py index 845644c..170b76e 100755 --- a/sbapp/kivymd/uix/refreshlayout/refreshlayout.py +++ b/sbapp/kivymd/uix/refreshlayout/refreshlayout.py @@ -7,12 +7,12 @@ Example .. code-block:: python - from kivymd.app import MDApp from kivy.clock import Clock from kivy.lang import Builder from kivy.factory import Factory from kivy.properties import StringProperty + from kivymd.app import MDApp from kivymd.uix.button import MDIconButton from kivymd.icon_definitions import md_icons from kivymd.uix.list import ILeftBodyTouch, OneLineIconListItem @@ -27,7 +27,7 @@ Example icon: root.icon - + MDBoxLayout: orientation: 'vertical' @@ -36,7 +36,7 @@ Example title: app.title md_bg_color: app.theme_cls.primary_color background_palette: 'Primary' - elevation: 10 + elevation: 4 left_action_items: [['menu', lambda x: x]] MDScrollViewRefreshLayout: @@ -112,10 +112,10 @@ from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ColorProperty, NumericProperty, ObjectProperty from kivy.uix.floatlayout import FloatLayout -from kivy.uix.scrollview import ScrollView from kivymd import uix_path from kivymd.theming import ThemableBehavior +from kivymd.uix.scrollview import MDScrollView with open( os.path.join(uix_path, "refreshlayout", "refreshlayout.kv"), @@ -150,7 +150,7 @@ class _RefreshScrollEffect(DampedScrollEffect): return False -class MDScrollViewRefreshLayout(ScrollView): +class MDScrollViewRefreshLayout(MDScrollView): root_layout = ObjectProperty() """ The spinner will be attached to this layout. @@ -159,22 +159,31 @@ class MDScrollViewRefreshLayout(ScrollView): and defaults to `None`. """ - def __init__(self, **kargs): - super().__init__(**kargs) + refresh_callback = ObjectProperty() + """ + The method that will be called at the on_touch_up event, + provided that the overscroll of the list has been registered. + + :attr:`refresh_callback` is a :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.effect_cls = _RefreshScrollEffect - self._work_spinnrer = False + self._work_spinner = False self._did_overscroll = False self.refresh_spinner = None def on_touch_up(self, *args): - if self._did_overscroll and not self._work_spinnrer: + if self._did_overscroll and not self._work_spinner: if self.refresh_callback: self.refresh_callback() if not self.refresh_spinner: self.refresh_spinner = RefreshSpinner(_refresh_layout=self) self.root_layout.add_widget(self.refresh_spinner) self.refresh_spinner.start_anim_spinner() - self._work_spinnrer = True + self._work_spinner = True self._did_overscroll = False return True @@ -219,5 +228,5 @@ class RefreshSpinner(ThemableBehavior, FloatLayout): spinner = self.ids.spinner spinner.size = (dp(30), dp(30)) spinner.opacity = 1 - self._refresh_layout._work_spinnrer = False + self._refresh_layout._work_spinner = False self._refresh_layout._did_overscroll = False diff --git a/sbapp/kivymd/uix/relativelayout.py b/sbapp/kivymd/uix/relativelayout.py index 96338a7..64f4658 100644 --- a/sbapp/kivymd/uix/relativelayout.py +++ b/sbapp/kivymd/uix/relativelayout.py @@ -2,13 +2,13 @@ Components/RelativeLayout ========================= -:class:`~kivy.uix.relativelayout.RelativeLayout` class equivalent. Simplifies working -with some widget properties. For example: +:class:`~kivy.uix.relativelayout.RelativeLayout` class equivalent. +Simplifies working with some widget properties. For example: RelativeLayout -------------- -.. code-block:: +.. code-block:: kv RelativeLayout: canvas: @@ -22,7 +22,7 @@ RelativeLayout MDRelativeLayout ---------------- -.. code-block:: +.. code-block:: kv MDRelativeLayout: radius: [25, ] @@ -32,7 +32,11 @@ MDRelativeLayout from kivy.uix.relativelayout import RelativeLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDRelativeLayout(RelativeLayout, MDAdaptiveWidget): - pass +class MDRelativeLayout(DeclarativeBehavior, RelativeLayout, MDAdaptiveWidget): + """ + Relative layout class. For more information, see in the + :class:`~kivy.uix.relativelayout.RelativeLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/responsivelayout.py b/sbapp/kivymd/uix/responsivelayout.py new file mode 100644 index 0000000..a3025b7 --- /dev/null +++ b/sbapp/kivymd/uix/responsivelayout.py @@ -0,0 +1,189 @@ +""" +Components/ResponsiveLayout +=========================== + +.. versionadded:: 1.0.0 + +.. rubric:: Responsive design is a graphic user interface (GUI) design + approach used to create content that adjusts smoothly to various screen + sizes. + +.. raw:: html + +
+ +
+ +The :class:`~MDResponsiveLayout` class does not reorganize your UI. Its task +is to track the size of the application screen and, depending on this size, +the :class:`~MDResponsiveLayout` class selects which UI layout should be +displayed at the moment: mobile, tablet or desktop. Therefore, if you want to +have a responsive view some kind of layout in your application, you should +have three KV files with UI markup for three platforms. + +You need to set three parameters for the :class:`~MDResponsiveLayout` class +:attr:`~MDResponsiveLayout.mobile_view`, +:attr:`~MDResponsiveLayout.tablet_view` and +:attr:`~MDResponsiveLayout.desktop_view`. These should be Kivy or KivyMD +widgets. + +Usage responsive +---------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.label import MDLabel + from kivymd.uix.responsivelayout import MDResponsiveLayout + from kivymd.uix.screen import MDScreen + + KV = ''' + + halign: "center" + + + + CommonComponentLabel: + text: "Mobile" + + + + CommonComponentLabel: + text: "Table" + + + + CommonComponentLabel: + text: "Desktop" + + + ResponsiveView: + ''' + + + class CommonComponentLabel(MDLabel): + pass + + + class MobileView(MDScreen): + pass + + + class TabletView(MDScreen): + pass + + + class DesktopView(MDScreen): + pass + + + class ResponsiveView(MDResponsiveLayout, MDScreen): + def __init__(self, **kw): + super().__init__(**kw) + self.mobile_view = MobileView() + self.tablet_view = TabletView() + self.desktop_view = DesktopView() + + + class Test(MDApp): + def build(self): + return Builder.load_string(KV) + + + Test().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/responsive-usage.gif + :align: center + +.. note:: Use common components for platform layouts (mobile, tablet, desktop views). + As shown in the example above, such a common component is the + `CommonComponentLabel` widget. + +Perhaps you expected more from the :class:`~MDResponsiveLayout` widget, but +even `Flutter` uses a similar approach to creating a responsive UI. + +You can also use the `commands `_ +provided to you by the developer tools to create a project with an responsive +design. +""" + +from kivy.event import EventDispatcher +from kivy.properties import ObjectProperty + +from kivymd.uix.controllers import WindowController + + +class MDResponsiveLayout(EventDispatcher, WindowController): + """ + :Events: + :attr:`on_change_screen_type` + Called when the screen type changes. + """ + + mobile_view = ObjectProperty() + """ + Mobile view. Must be a Kivy or KivyMD widget. + + :attr:`mobile_view` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + tablet_view = ObjectProperty() + """ + Tablet view. Must be a Kivy or KivyMD widget. + + :attr:`tablet_view` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + desktop_view = ObjectProperty() + """ + Desktop view. Must be a Kivy or KivyMD widget. + + :attr:`desktop_view` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + _current_device_type = "" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_change_screen_type") + + def on_change_screen_type(self, *args): + """Called when the screen type changes.""" + + def on_size(self, *args) -> None: + """Called when the application screen size changes.""" + + super().on_size(*args) + self.set_screen() + + if self._current_device_type != self.real_device_type: + self._current_device_type = self.real_device_type + + def set_screen(self) -> None: + """ + Sets the screen according to the type of application screen size: + mobile/tablet or desktop view. + """ + + if self.real_device_type != self._current_device_type: + self.clear_widgets() + + if self.mobile_view and self.tablet_view and self.desktop_view: + if self.real_device_type == "mobile": + self.add_widget(self.mobile_view) + elif self.real_device_type == "tablet": + self.add_widget(self.tablet_view) + elif self.real_device_type == "desktop": + self.add_widget(self.desktop_view) + + self.dispatch("on_change_screen_type", self.real_device_type) diff --git a/sbapp/kivymd/uix/screen.py b/sbapp/kivymd/uix/screen.py index 40798cf..2e399ec 100644 --- a/sbapp/kivymd/uix/screen.py +++ b/sbapp/kivymd/uix/screen.py @@ -8,7 +8,7 @@ with some widget properties. For example: Screen ------ -.. code-block:: +.. code-block:: kv Screen: canvas: @@ -22,35 +22,56 @@ Screen MDScreen -------- -.. code-block:: +.. code-block:: kv MDScreen: radius: [25, 0, 0, 0] md_bg_color: app.theme_cls.primary_color """ -from kivy.properties import ObjectProperty +from kivy.properties import ListProperty, ObjectProperty from kivy.uix.screenmanager import Screen from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior from kivymd.uix.hero import MDHeroTo -class MDScreen(Screen, MDAdaptiveWidget): - hero_to = ObjectProperty() +class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget): """ - Must be a :class:`~kivymd.uix.hero.MDHeroTo` class. + Screen is an element intended to be used with a + :class:`~kivymd.uix.screenmanager.MDScreenManager`. For more information, + see in the :class:`~kivy.uix.screenmanager.Screen` class documentation. + """ + + hero_to = ObjectProperty(deprecated=True) + """ + Must be a :class:`~kivymd.uix.hero.MDHeroTo` class. + See the documentation of the `MDHeroTo `_ widget for more detailed information. - .. versionchanged:: 1.0.0 + .. deprecated:: 1.0.0 + Use attr:`heroes_to` attribute instead. :attr:`hero_to` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ - def on_hero_to(self, screen, widget) -> None: + heroes_to = ListProperty() + """ + Must be a list of :class:`~kivymd.uix.hero.MDHeroTo` class. + + .. versionadded:: 1.0.0 + + :attr:`heroes_to` is an :class:`~kivy.properties.LiatProperty` + and defaults to `[]`. + """ + + def on_hero_to(self, screen, widget: MDHeroTo) -> None: + """Called when the value of the :attr:`hero_to` attribute changes.""" + if not isinstance(widget, MDHeroTo) or not issubclass( widget.__class__, MDHeroTo ): @@ -58,3 +79,4 @@ class MDScreen(Screen, MDAdaptiveWidget): f"The `{widget}` widget must be an `kivymd.uix.hero.MDHeroTo` " f"class or inherited from this class" ) + self.heroes_to = [widget] diff --git a/sbapp/kivymd/uix/screenmanager.py b/sbapp/kivymd/uix/screenmanager.py index 1e23b68..55b6c0e 100644 --- a/sbapp/kivymd/uix/screenmanager.py +++ b/sbapp/kivymd/uix/screenmanager.py @@ -5,37 +5,64 @@ Components/ScreenManager .. versionadded:: 1.0.0 :class:`~kivy.uix.screenmanager.ScreenManager` class equivalent. -If you want to use Hero animations you need to use :class:`~kivymd.uix.screenmanager.MDScreenManager` -not :class:`~kivy.uix.screenmanager.ScreenManager` class. +If you want to use Hero animations you need to use +:class:`~kivymd.uix.screenmanager.MDScreenManager` not +:class:`~kivy.uix.screenmanager.ScreenManager` class. """ +from kivy import Logger from kivy.clock import Clock from kivy.properties import ListProperty, StringProperty from kivy.uix.screenmanager import ScreenManager +from kivymd.uix.behaviors import DeclarativeBehavior from kivymd.uix.hero import MDHeroFrom -class MDScreenManager(ScreenManager): - current_hero = StringProperty(None) +class MDScreenManager(DeclarativeBehavior, ScreenManager): """ - The name of the current tag for the :class:`~kivymd.uix.hero.MDHeroFrom` and - :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when + Screen manager. This is the main class that will control your + :class:`~kivymd.uix.screen.MDScreen` stack and memory. + + For more + information, see in the :class:`~kivy.uix.screenmanager.ScreenManager` + class documentation. + """ + + current_hero = StringProperty(None, deprecated=True) + """ + The name of the current tag for the :class:`~kivymd.uix.hero.MDHeroFrom` + and :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when animating the transition between screens. + .. deprecated:: 1.1.0 + Use :attr:`current_heroes` attribute instead. + See the `Hero `_ - module documentation for more information about creating and using Hero animations. + module documentation for more information about creating and using Hero + animations. :attr:`current_hero` is an :class:`~kivy.properties.StringProperty` and defaults to `None`. """ + current_heroes = ListProperty() + """ + A list of names (tags) of heroes that need to be animated when moving + to the next screen. + + .. versionadded:: 1.1.0 + + :attr:`current_heroes` is an :class:`~kivy.properties.ListProperty` + and defaults to `[]`. + """ + # Collection of `MDHeroFrom` objects on all screens of the current # screen manager. _heroes_data = ListProperty() - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) Clock.schedule_once(self.check_transition) def check_transition(self, *args) -> None: @@ -48,28 +75,48 @@ class MDScreenManager(ScreenManager): self.transition = MDSlideTransition() - def get_hero_from_widget(self) -> None: + def get_hero_from_widget(self) -> list: """ - Get an :class:`~kivymd.uix.hero.MDHeroTo` object with the - :attr:`~current_hero` tag. + Get a list of :class:`~kivymd.uix.hero.MDHeroFrom` objects according + to the tag names specified in the :attr:`~current_heroes` list. """ - hero_from_widget = None + hero_from_widget = [] - for hero_widget in self._heroes_data: - if isinstance(hero_widget, MDHeroFrom) or issubclass( - hero_widget.__class__, MDHeroFrom - ): - if hero_widget.tag == self.current_hero: - hero_from_widget = hero_widget - break + for name_hero in self.current_heroes: + for hero_widget in self._heroes_data: + if isinstance(hero_widget, MDHeroFrom) or issubclass( + hero_widget.__class__, MDHeroFrom + ): + if hero_widget.tag == name_hero: + hero_from_widget.append(hero_widget) return hero_from_widget + def on_current_hero(self, instance, value: str) -> None: + """ + Called when the value of the :attr:`current_hero` attribute changes. + """ + + Logger.warning( + "KivyMD: " + "`kivymd/uix/screenmanager.MDScreenManager.current_hero` " + "attribute is deprecated. " + "Use `kivymd/uix/screenmanager.MDScreenManager.current_heroes` " + "attribute instead." + ) + if value: + self.current_heroes = [value] + else: + self.current_heroes = [] + def add_widget(self, widget, *args, **kwargs): super().add_widget(widget, *args, **kwargs) Clock.schedule_once(lambda x: self._create_heroes_data(widget)) + # TODO: Add a method to delete an object from the arrt:`_heroes_data` + # collection when deleting an object using the `remove_widget` method. + def _create_heroes_data(self, widget): def find_hero_widget(child_widget): widget_hero = None diff --git a/sbapp/kivymd/uix/scrollview.py b/sbapp/kivymd/uix/scrollview.py new file mode 100644 index 0000000..0b00061 --- /dev/null +++ b/sbapp/kivymd/uix/scrollview.py @@ -0,0 +1,49 @@ +""" +Components/ScrollView +===================== + +.. versionadded:: 1.0.0 + +:class:`~kivy.uix.scrollview.ScrollView` class equivalent. Simplifies working +with some widget properties. For example: + +ScrollView +---------- + +.. code-block:: kv + + ScrollView: + + canvas: + Color: + rgba: app.theme_cls.primary_color + Rectangle: + pos: self.pos + size: self.size + +MDScrollView +------------ + +.. code-block:: kv + + MDScrollView: + md_bg_color: app.theme_cls.primary_color +""" + +__all__ = ("MDScrollView",) + +from kivy.uix.scrollview import ScrollView + +from kivymd.uix.behaviors import ( + DeclarativeBehavior, + SpecificBackgroundColorBehavior, +) + + +class MDScrollView( + DeclarativeBehavior, SpecificBackgroundColorBehavior, ScrollView +): + """ + ScrollView class. For more information, see in the + :class:`~kivy.uix.scrollview.ScrollView` class documentation. + """ diff --git a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv index 9aed57f..3ac3656 100644 --- a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv +++ b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv @@ -15,8 +15,11 @@ pos_hint: {"center_y": .5} x: root._segment_switch_x md_bg_color: root.segment_color - elevation: 6 + elevation: 2 _radius: root.radius[0] - 4 + width: + segment_panel.width / segment_panel.children_number \ + - segment_panel.spacing SegmentPanel: id: segment_panel diff --git a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py index 243ae9a..5e9c7f4 100644 --- a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py +++ b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py @@ -10,58 +10,77 @@ Components/SegmentedControl Usage ===== -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV style - from kivymd.app import MDApp + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp - KV = ''' - MDScreen: + KV = ''' + MDScreen: - MDSegmentedControl: - pos_hint: {"center_x": .5, "center_y": .5} + MDSegmentedControl: + pos_hint: {"center_x": .5, "center_y": .5} - MDSegmentedControlItem: - text: "Male" + MDSegmentedControlItem: + text: "Male" - MDSegmentedControlItem: - text: "Female" + MDSegmentedControlItem: + text: "Female" - MDSegmentedControlItem: - text: "All" - ''' + MDSegmentedControlItem: + text: "All" + ''' - class Test(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - Test().run() + Example().run() -Or only in python code: + .. tab:: Declarative python style -.. code-block:: python + .. code-block:: python - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.segmentedcontrol import MDSegmentedControl, MDSegmentedControlItem + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.segmentedcontrol import ( + MDSegmentedControl, MDSegmentedControlItem + ) - class Test(MDApp): - def build(self): - screen = MDScreen() - segment_control = MDSegmentedControl(pos_hint={"center_x": .5, "center_y": .5}) - segment_control.add_widget(MDSegmentedControlItem(text="Male")) - segment_control.add_widget(MDSegmentedControlItem(text="Female")) - segment_control.add_widget(MDSegmentedControlItem(text="All")) - screen.add_widget(segment_control) - return screen + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDSegmentedControl( + MDSegmentedControlItem( + text="Male" + ), + MDSegmentedControlItem( + text="Female" + ), + MDSegmentedControlItem( + text="All" + ), + pos_hint={"center_x": 0.5, "center_y": 0.5} + ) + ) + ) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-usage.gif :align: center @@ -117,12 +136,22 @@ with open( class MDSegmentedControlItem(MDLabel): - """Implements a label to place on the :class:`~SegmentPanel` panel.""" + """ + Implements a label to place on the :class:`~SegmentPanel` panel. + + See :class:`~kivymd.uix.label.MDLabel` class documentation for more + information. + """ # TODO: Add an attribute for the color of the active segment label. -class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): +class MDSegmentedControl(MDRelativeLayout, ThemableBehavior): """ + Implements a segmented control panel. + + Relative layout class. For more information, see in the + :class:`~kivy.uix.relativelayout.RelativeLayout` class documentation. + :Events: `on_active` Called when the segment is activated. @@ -135,7 +164,7 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): .. code-block:: kv MDSegmentedControl: - md_bg_color: "#451938" + md_bg_color: "brown" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-md-bg-color.png :align: center @@ -151,8 +180,8 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): .. code-block:: kv MDSegmentedControl: - md_bg_color: "#451938" - segment_color: "#e4514f" + md_bg_color: "brown" + segment_color: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png :align: center @@ -160,8 +189,8 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): .. code-block:: kv MDSegmentedControl: - md_bg_color: "#451938" - segment_color: "#e4514f" + md_bg_color: "brown" + segment_color: "red" MDSegmentedControlItem: text: "[color=fff]Male[/color]" @@ -196,9 +225,9 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): .. code-block:: kv MDSegmentedControl: - md_bg_color: "#451938" - segment_color: "#e4514f" - separator_color: 1, 1, 1, 1 + md_bg_color: "brown" + segment_color: "red" + separator_color: "white" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png :align: center @@ -249,15 +278,12 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): _segment_switch_x = NumericProperty(dp(4)) - def __init__(self, **kw): - super().__init__(**kw) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_active") Clock.schedule_once(self.set_default_colors) Clock.schedule_once(self._remove_last_separator) - # FIXME: Sometimes this interval is not enough to get the width - # of the segment label textures. - Clock.schedule_once(self._set_width_segment_switch, 2.2) def set_default_colors(self, *args) -> None: """ @@ -313,6 +339,10 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): self.ids.segment_panel.add_widget(widget) separator = MDSeparator(orientation="vertical") self.ids.segment_panel.add_widget(separator) + if not self.ids.segment_panel._started: + self.ids.segment_panel._started = True + else: + self.ids.segment_panel.children_number += 1 Clock.schedule_once( lambda x: self.update_separator_color(separator) ) @@ -326,15 +356,6 @@ class MDSegmentedControl(ThemableBehavior, MDRelativeLayout): self.current_active_segment = widget self.dispatch("on_active", widget) - def _set_width_segment_switch(self, *args): - """ - Sets the width of the switch. I think this is not done quite correctly. - """ - - self.ids.segment_switch.width = self.ids.segment_panel.children[ - 0 - ].width + dp(12) - def _remove_last_separator(self, *args): self.ids.segment_panel.remove_widget(self.ids.segment_panel.children[0]) @@ -350,3 +371,7 @@ class SegmentPanel(MDBoxLayout): Implements a panel for placing items - :class:`~MDSegmentedControlItem` for the :class:`~MDSegmentedControl` class. """ + + children_number = NumericProperty(1) + + _started = BooleanProperty(defaultvalue=False) diff --git a/sbapp/kivymd/uix/selection/selection.py b/sbapp/kivymd/uix/selection/selection.py index c5c9556..a82804f 100644 --- a/sbapp/kivymd/uix/selection/selection.py +++ b/sbapp/kivymd/uix/selection/selection.py @@ -50,6 +50,7 @@ Example with TwoLineAvatarListItem from kivy.animation import Animation from kivy.lang import Builder + from kivy.utils import get_color_from_hex from kivymd.app import MDApp from kivymd.uix.list import TwoLineAvatarListItem @@ -100,7 +101,7 @@ Example with TwoLineAvatarListItem class Example(MDApp): - overlay_color = "#6042e4" + overlay_color = get_color_from_hex("#6042e4") def build(self): return Builder.load_string(KV) @@ -156,7 +157,7 @@ Example with FitImage from kivy.properties import ColorProperty from kivymd.app import MDApp - from kivymd.utils.fitimage import FitImage + from kivymd.uix.fitimage import FitImage KV = ''' MDBoxLayout: @@ -446,7 +447,7 @@ class SelectionItem(ThemableBehavior, MDRelativeLayout, TouchBehavior): def get_progress_round_pos(self) -> tuple: return ( - self.center_x - self.progress_round_size / 2, + (self.pos[0] + self.width / 2) - self.progress_round_size / 2, self.center_y - self.progress_round_size / 2, ) diff --git a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv index bfbfcda..3d3b015 100644 --- a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv +++ b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv @@ -1,3 +1,7 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import colors kivymd.color_definitions.colors + + canvas: Clear @@ -16,59 +20,126 @@ - color: 1, 1, 1, 1 - canvas: - Color: - rgba: self.color - Ellipse: - size: self.size - pos: self.pos + radius: self.width / 2 + ripple_scale: 2 + + ThumbIcon: + id: icon + font_size: "16sp" + theme_text_color: "Custom" + pos_hint: {"center_x": .5, "center_y": .5} canvas.before: Color: rgba: - self._track_color_disabled if self.disabled else \ ( \ - self._track_color_active \ - if self.active else self._track_color_normal \ + self.track_color_disabled \ + if self.track_color_disabled else \ + self.theme_cls.disabled_hint_text_color) \ + if self.disabled else \ + ( \ + ( \ + self.track_color_active \ + if self.track_color_active else \ + self.theme_cls.primary_color[:-1] + [.5]) \ + if self.active else \ + (self.track_color_inactive \ + if self.track_color_inactive else \ + self.theme_cls.disabled_hint_text_color) \ ) RoundedRectangle: size: (self.width + dp(14), dp(28)) \ if root.widget_style == "ios" else \ - (self.width - dp(8), dp(16)) + ( \ + (self.width - dp(8), dp(16)) \ + if app.theme_cls.material_style == "M2" else \ + (self.width + dp(16), dp(32)) \ + ) pos: (self.x - dp(2), self.center_y - dp(14)) \ if root.widget_style == "ios" else \ (self.x + dp(8), self.center_y - dp(8)) radius: - [dp(14)] if root.widget_style == "ios" else [dp(7)] + [dp(14)] \ + if root.widget_style == "ios" else \ + [dp(7) if app.theme_cls.material_style == "M2" else dp(16)] Color: rgba: ( \ - self.theme_cls.disabled_hint_text_color[:-1] + [.2] \ + self.theme_cls.disabled_hint_text_color \ if not root.active else (0, 0, 0, 0) \ ) \ - if root.widget_style == "ios" else (0, 0, 0, 0) - Line: - width: 1 + if root.widget_style == "ios" \ + or app.theme_cls.material_style == "M3" else \ + (0, 0, 0, 0) + SmoothLine: + width: + 1 \ + if root.widget_style == "ios" \ + or app.theme_cls.material_style == "M2" else \ + 1.4 rounded_rectangle: ( \ self.x - dp(2), self.center_y - dp(14), self.width + dp(14), \ dp(28), dp(14), dp(14), dp(14), dp(14), dp(28) \ ) \ if root.widget_style == "ios" else \ - (1, 1, 1, 1, 1, 1, 1, 1, 1) + ( \ + (1, 1, 1, 1, 1, 1, 1, 1, 1) \ + if app.theme_cls.material_style == "M2" else \ + ( \ + self.x + dp(8), self.center_y - dp(8), self.width + dp(16), \ + dp(32), dp(16), dp(16), dp(16), dp(16), dp(32) \ + ) + ) Thumb: id: thumb size_hint: None, None size: dp(24), dp(24) - pos: root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1] - color: - root.thumb_color_disabled if root.disabled else \ - (root.thumb_color_down if root.active else root.thumb_color) - elevation: 8 if root.active else 5 - on_release: setattr(root, "active", not root.active) + elevation: + (2.5 if root.active else 1) \ + if app.theme_cls.material_style != "M3" else \ + 0 + pos: + (root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1]) \ + if root.widget_style == "ios" \ + or app.theme_cls.material_style == "M2" else \ + ( \ + root.pos[0] + self.width + root._thumb_pos[0], \ + root.pos[1] + (root.height / 2 - self.height / 2) + root._thumb_pos[1] \ + ) + _no_ripple_effect: + True \ + if app.theme_cls.material_style == "M3" \ + and root.widget_style != "ios" else \ + False + md_bg_color: + ( \ + root.thumb_color_disabled \ + if root.thumb_color_disabled else \ + get_color_from_hex(colors["Gray"]["800"]) \ + ) \ + if root.disabled else \ + ( \ + (root.thumb_color_active \ + if root.thumb_color_active else \ + root.theme_cls.primary_color \ + ) \ + if root.active else \ + ( \ + root.thumb_color_inactive \ + if root.thumb_color_inactive else \ + get_color_from_hex(colors["Gray"]["50"] \ + ) \ + ) \ + ) + on_touch_down: + if self.collide_point(*args[1].pos) and not root.disabled: \ + root.on_thumb_down() + on_touch_up: + if self.collide_point(*args[1].pos) and not root.disabled: \ + setattr(root, "active", not root.active) diff --git a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py index d0503af..031fd3a 100755 --- a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py +++ b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py @@ -51,9 +51,6 @@ MDCheckbox ``(dp(48), dp(48))``, but the ripple effect takes up all the available space. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-no-size.gif - :align: center - Control state ------------- @@ -148,6 +145,35 @@ MDSwitch .. Note:: Control state of :class:`~MDSwitch` same way as in :class:`~MDCheckbox`. + +MDSwitch in M3 style +-------------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDSwitch: + pos_hint: {'center_x': .5, 'center_y': .5} + active: True + ''' + + + class Test(MDApp): + def build(self): + self.theme_cls.material_style = "M3" + return Builder.load_string(KV) + + + Test().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-m3.gif + :align: center """ __all__ = ("MDCheckbox", "MDSwitch") @@ -159,26 +185,18 @@ from kivy.clock import Clock from kivy.lang import Builder from kivy.metrics import dp, sp from kivy.properties import ( - AliasProperty, BooleanProperty, ColorProperty, ListProperty, - NumericProperty, - OptionProperty, StringProperty, ) -from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior +from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.floatlayout import FloatLayout -from kivy.uix.widget import Widget -from kivy.utils import get_color_from_hex from kivymd import uix_path -from kivymd.color_definitions import colors from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - CircularRippleBehavior, - FakeCircularElevationBehavior, -) +from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior +from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.label import MDIcon with open( @@ -233,30 +251,84 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): and defaults to `'checkbox-marked-circle'`. """ - selected_color = ColorProperty(None) + color_active = ColorProperty(None) """ - Selected color in ``rgba`` format. + Color when the checkbox is in the active state. - :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDCheckbox: + color_active: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-color-active.png + :align: center + + :attr:`color_active` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - unselected_color = ColorProperty(None) + color_inactive = ColorProperty(None) """ - Unelected color in ``rgba`` format. + Color when the checkbox is in the inactive state. - :attr:`unselected_color` is a :class:`~kivy.properties.ColorProperty` + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDCheckbox: + color_inactive: "blue" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-color-inactive.png + :align: center + + :attr:`color_inactive` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ disabled_color = ColorProperty(None) """ - Disabled color in ``rgba`` format. + Color when the checkbox is in the disabled state. + + .. code-block:: kv + + MDCheckbox: + disabled_color: "lightgrey" + disabled: True + active: True + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-disabled-color.png + :align: center :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ + # Deprecated property. + + selected_color = ColorProperty(None, deprecated=True) + """ + Color when the checkbox is in the active state. + + .. deprecated:: 1.0.0 + Use :attr:`color_active` instead. + + :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + unselected_color = ColorProperty(None, deprecated=True) + """ + Color when the checkbox is in the inactive state. + + .. deprecated:: 1.0.0 + Use :attr:`color_inactive` instead. + + :attr:`unselected_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + _current_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) def __init__(self, **kwargs): @@ -265,10 +337,10 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): font_size=sp(24), duration=0.1, t="out_quad" ) super().__init__(**kwargs) - self.selected_color = self.theme_cls.primary_color - self.unselected_color = self.theme_cls.secondary_text_color + self.color_active = self.theme_cls.primary_color + self.color_inactive = self.theme_cls.secondary_text_color self.disabled_color = self.theme_cls.divider_color - self._current_color = self.unselected_color + self._current_color = self.color_inactive self.check_anim_out.bind( on_complete=lambda *x: self.check_anim_in.start(self) ) @@ -278,27 +350,29 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): radio_icon_normal=self.update_icon, radio_icon_down=self.update_icon, group=self.update_icon, - selected_color=self.update_color, - unselected_color=self.update_color, + color_active=self.update_color, + color_inactive=self.update_color, disabled_color=self.update_color, disabled=self.update_color, state=self.update_color, ) - self.theme_cls.bind(primary_color=self.update_primary_color) - self.theme_cls.bind(theme_style=self.update_primary_color) + self.theme_cls.bind( + theme_style=self.update_primary_color, + primary_color=self.update_primary_color, + ) self.update_icon() self.update_color() - def update_primary_color(self, instance, value): + def update_primary_color(self, instance, value) -> None: if value in ("Dark", "Light"): if not self.disabled: self.color = self.theme_cls.primary_color else: self.color = self.disabled_color else: - self.selected_color = value + self.color_active = value - def update_icon(self, *args): + def update_icon(self, *args) -> None: if self.state == "down": self.icon = ( self.radio_icon_down if self.group else self.checkbox_icon_down @@ -310,15 +384,15 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): else self.checkbox_icon_normal ) - def update_color(self, *args): + def update_color(self, *args) -> None: if self.disabled: self._current_color = self.disabled_color elif self.state == "down": - self._current_color = self.selected_color + self._current_color = self.color_active else: - self._current_color = self.unselected_color + self._current_color = self.color_inactive - def on_state(self, *args): + def on_state(self, *args) -> None: if self.state == "down": self.check_anim_in.cancel(self) self.check_anim_out.start(self) @@ -333,22 +407,25 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): self.update_icon() self.active = False - def on_active(self, *args): + def on_active(self, *args) -> None: self.state = "down" if self.active else "normal" -class Thumb( - FakeCircularElevationBehavior, - CircularRippleBehavior, - ButtonBehavior, - Widget, -): - ripple_scale = NumericProperty(2) +class ThumbIcon(MDIcon): """ - See :attr:`~kivymd.uix.behaviors.ripplebehavior.CommonRipple.ripple_scale`. + Implements icon for the :class:`~Thumb` widget. - :attr:`ripple_scale` is a :class:`~kivy.properties.NumericProperty` - and defaults to `2`. + .. versionadded:: 1.0.0 + """ + + +class Thumb( + CommonElevationBehavior, + CircularRippleBehavior, + MDFloatLayout, +): + """ + Implements a thumb for the :class:`~MDSwitch` widget. """ def _set_ellipse(self, instance, value): @@ -365,7 +442,7 @@ class Thumb( ) -class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): +class MDSwitch(ThemableBehavior, FloatLayout): active = BooleanProperty(False) """ Indicates if the switch is active or inactive. @@ -374,161 +451,309 @@ class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): and defaults to `False`. """ - _thumb_color = ColorProperty(get_color_from_hex(colors["Gray"]["50"])) - - def _get_thumb_color(self): - return self._thumb_color - - def _set_thumb_color(self, color, alpha=None): - if len(color) == 2: - self._thumb_color = get_color_from_hex(colors[color[0]][color[1]]) - if alpha: - self._thumb_color[3] = alpha - elif len(color) == 4: - self._thumb_color = color - - thumb_color = AliasProperty( - _get_thumb_color, _set_thumb_color, bind=["_thumb_color"] - ) + icon_active = StringProperty() """ - Get thumb color ``rgba`` format. + Thumb icon when the switch is in the active state (only M3 style). - :attr:`thumb_color` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + active: True + icon_active: "check" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-icon-active.png + :align: center + + :attr:`icon_active` is a :class:`~kivy.properties.StringProperty` + and defaults to `''`. """ - _thumb_color_down = ColorProperty([1, 1, 1, 1]) - - def _get_thumb_color_down(self): - return self._thumb_color_down - - def _set_thumb_color_down(self, color, alpha=None): - if len(color) == 2: - self._thumb_color_down = get_color_from_hex( - colors[color[0]][color[1]] - ) - if alpha: - self._thumb_color_down[3] = alpha - else: - self._thumb_color_down[3] = 1 - elif len(color) == 4: - self._thumb_color_down = color - - _thumb_color_disabled = ColorProperty( - get_color_from_hex(colors["Gray"]["400"]) - ) - - thumb_color_disabled = get_color_from_hex(colors["Gray"]["800"]) + icon_inactive = StringProperty() """ - Get thumb color disabled ``rgba`` format. + Thumb icon when the switch is in an inactive state (only M3 style). - :attr:`thumb_color_disabled` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + icon_inactive: "close" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-icon-inactive.png + :align: center + + :attr:`icon_inactive` is a :class:`~kivy.properties.StringProperty` + and defaults to `''`. """ - def _get_thumb_color_disabled(self): - return self._thumb_color_disabled - - def _set_thumb_color_disabled(self, color, alpha=None): - if len(color) == 2: - self._thumb_color_disabled = get_color_from_hex( - colors[color[0]][color[1]] - ) - if alpha: - self._thumb_color_disabled[3] = alpha - elif len(color) == 4: - self._thumb_color_disabled = color - - thumb_color_down = AliasProperty( - _get_thumb_color_disabled, - _set_thumb_color_disabled, - bind=["_thumb_color_disabled"], - ) + icon_active_color = ColorProperty(None) """ - Get thumb color down ``rgba`` format. + Thumb icon color when the switch is in the active state (only M3 style). - :attr:`thumb_color_down` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + active: True + icon_active: "check" + icon_active_color: "white" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-icon-active-color.png + :align: center + + :attr:`icon_active_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - theme_thumb_color = OptionProperty("Primary", options=["Primary", "Custom"]) + icon_inactive_color = ColorProperty(None) """ - Thumb color scheme name + Thumb icon color when the switch is in an inactive state (only M3 style). - :attr:`theme_thumb_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `Primary`. + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + icon_inactive: "close" + icon_inactive_color: "grey" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-icon-inactive-color.png + :align: center + + :attr:`icon_inactive_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - theme_thumb_down_color = OptionProperty( - "Primary", options=["Primary", "Custom"] - ) + thumb_color_active = ColorProperty(None) """ - Thumb Down color scheme name + The color of the thumb when the switch is active. - :attr:`theme_thumb_down_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `Primary`. + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + active: True + thumb_color_active: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-thumb-color-active.png + :align: center + + :attr:`thumb_color_active` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + thumb_color_inactive = ColorProperty(None) + """ + The color of the thumb when the switch is inactive. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + thumb_color_inactive: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-thumb-color-inactive.png + :align: center + + :attr:`thumb_color_inactive` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + thumb_color_disabled = ColorProperty(None) + """ + The color of the thumb when the switch is in the disabled state. + + .. code-block:: kv + + MDSwitch: + active: True + thumb_color_disabled: "brown" + disabled: True + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-thumb-color-disabled.png + :align: center + + :attr:`thumb_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_active = ColorProperty(None) + """ + The color of the track when the switch is active. + + .. code-block:: kv + + MDSwitch: + active: True + track_color_active: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-track-color-active.png + :align: center + + :attr:`track_color_active` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_inactive = ColorProperty(None) + """ + The color of the track when the switch is inactive. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSwitch: + track_color_inactive: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-track-color-inactive.png + :align: center + + :attr:`track_color_inactive` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_disabled = ColorProperty(None) + """ + The color of the track when the switch is in the disabled state. + + .. code-block:: kv + + MDSwitch: + track_color_disabled: "lightgrey" + disabled: True + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-track-color-disabled.png + :align: center + + :attr:`track_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. """ - _track_color_active = ColorProperty([0, 0, 0, 0]) - _track_color_normal = ColorProperty([0, 0, 0, 0]) - _track_color_disabled = ColorProperty([0, 0, 0, 0]) _thumb_pos = ListProperty([0, 0]) def __init__(self, **kwargs): super().__init__(**kwargs) - self.theme_cls.bind( - theme_style=self._set_colors, - primary_color=self._set_colors, - primary_palette=self._set_colors, - ) - self.bind(active=self._update_thumb_pos) - Clock.schedule_once(self._set_colors) + self.bind(icon_active=self.set_icon, icon_inactive=self.set_icon) self.size_hint = (None, None) self.size = (dp(36), dp(48)) + Clock.schedule_once(self._check_style) + Clock.schedule_once(lambda x: self._update_thumb_pos(animation=False)) + Clock.schedule_once(lambda x: self.on_active(self, self.active)) - def _set_colors(self, *args): - self._track_color_normal = self.theme_cls.disabled_hint_text_color - if self.theme_cls.theme_style == "Dark": + def set_icon(self, instance_switch, icon_value: str) -> None: + def set_icon(*args): + icon = icon_value if icon_value else "blank" + self.ids.thumb.ids.icon.icon = icon - if self.theme_thumb_down_color == "Primary": - self._track_color_active = self.theme_cls.primary_color - else: - self._track_color_active = self.thumb_color_down + Clock.schedule_once(set_icon, 0.2) - self._track_color_active[3] = 0.5 - self._track_color_disabled = get_color_from_hex("FFFFFF") - self._track_color_disabled[3] = 0.1 - - if self.theme_thumb_color == "Primary": - self.thumb_color = get_color_from_hex(colors["Gray"]["400"]) - - if self.theme_thumb_down_color == "Primary": - self.thumb_color_down = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] + def on_active(self, instance_switch, active_value: bool) -> None: + if self.theme_cls.material_style == "M3" and self.widget_style != "ios": + size = ( + ( + (dp(16), dp(16)) + if not self.icon_inactive + else (dp(24), dp(24)) ) - else: - if self.theme_thumb_down_color == "Primary": - self._track_color_active = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] + if not active_value + else (dp(24), dp(24)) + ) + icon = "blank" + color = (0, 0, 0, 0) + + if self.icon_active and active_value: + icon = self.icon_active + color = ( + self.icon_active_color + if self.icon_active_color + else self.theme_cls.text_color + ) + elif self.icon_inactive and not active_value: + icon = self.icon_inactive + color = ( + self.icon_inactive_color + if self.icon_inactive_color + else self.theme_cls.text_color + ) + + Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb) + Animation(color=color, t="out_quad", d=0.2).start( + self.ids.thumb.ids.icon + ) + self.set_icon(self, icon) + + self._update_thumb_pos() + + # FIXME: If you move the cursor from the switch during the + # `on_touch_down` event, the animation of returning the thumb to + # the previous size does not work. The following code fixes this. + def on_thumb_down(self) -> None: + """ + Called at the on_touch_down event of the :class:`~Thumb` object. + Indicates the state of the switch "on/off" by an animation of + increasing the size of the thumb. + """ + + if self.widget_style != "ios" and self.theme_cls.material_style == "M3": + if self.active: + size = (dp(28), dp(28)) + pos = ( + self.ids.thumb.pos[0] - dp(2), + self.ids.thumb.pos[1] - dp(1.8), ) else: - self._track_color_active = self.thumb_color_down - - self._track_color_active[3] = 0.5 - self._track_color_disabled = self.theme_cls.disabled_hint_text_color - - if self.theme_thumb_down_color == "Primary": - self.thumb_color_down = self.theme_cls.primary_color - - if self.theme_thumb_color == "Primary": - self.thumb_color = get_color_from_hex(colors["Gray"]["50"]) + size = (dp(26), dp(26)) + pos = ( + ( + self.ids.thumb.pos[0] - dp(5), + self.ids.thumb.pos[1] - dp(5), + ) + if not self.icon_inactive + else ( + self.ids.thumb.pos[0] + dp(1), + self.ids.thumb.pos[1] - dp(1), + ) + ) + Animation(size=size, pos=pos, t="out_quad", d=0.2).start( + self.ids.thumb + ) def _update_thumb_pos(self, *args, animation=True): if self.active: - _thumb_pos = (self.width - dp(14), self.height / 2 - dp(12)) + _thumb_pos = ( + self.width + - ( + dp(14) + if self.widget_style == "ios" + or self.theme_cls.material_style == "M2" + else dp(28) + ), + self.height / 2 + - ( + dp(12) + if self.widget_style == "ios" + or self.theme_cls.material_style == "M2" + else dp(16) + ), + ) else: - _thumb_pos = (0, self.height / 2 - dp(12)) + _thumb_pos = ( + 0 if not self.icon_inactive else dp(-14), + self.height / 2 + - ( + dp(12) + if self.widget_style == "ios" + or self.theme_cls.material_style == "M2" + else dp(16) + ), + ) Animation.cancel_all(self, "_thumb_pos") + if animation: Animation(_thumb_pos=_thumb_pos, duration=0.2, t="out_quad").start( self @@ -536,5 +761,6 @@ class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): else: self._thumb_pos = _thumb_pos - def on_size(self, *args): - self._update_thumb_pos(animation=False) + def _check_style(self, *args): + if self.widget_style == "ios" or self.theme_cls.material_style == "M2": + self.set_icon(self, "") diff --git a/sbapp/kivymd/uix/slider/slider.kv b/sbapp/kivymd/uix/slider/slider.kv index 9af6ce5..5332381 100644 --- a/sbapp/kivymd/uix/slider/slider.kv +++ b/sbapp/kivymd/uix/slider/slider.kv @@ -1,8 +1,9 @@ -#:import images_path kivymd.images_path #:import Thumb kivymd.uix.selectioncontrol.Thumb +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import colors kivymd.color_definitions.colors - + @@ -10,9 +11,32 @@ Clear Color: rgba: - self._track_color_disabled if self.disabled \ - else (self._track_color_active if self.active \ - else self._track_color_normal) + ( \ + self.track_color_disabled \ + if self.track_color_disabled else \ + self.theme_cls.disabled_hint_text_color) \ + if self.disabled else \ + ( \ + ( \ + self.track_color_active \ + if self.track_color_active else \ + ( \ + get_color_from_hex(colors["Gray"]["400"]) \ + if app.theme_cls.theme_style == "Light" else \ + (1, 1, 1, .3) \ + ) \ + ) \ + if self.active else \ + ( \ + self.track_color_inactive \ + if self.track_color_inactive else \ + ( \ + self.theme_cls.disabled_hint_text_color \ + if app.theme_cls.theme_style == "Light" else \ + get_color_from_hex(colors["Gray"]["800"]) \ + ) \ + ) \ + ) Rectangle: size: (self.width - self.padding * 2 - self._offset[0], dp(4)) if \ @@ -26,10 +50,16 @@ # If 0 draw circle Color: rgba: - (0, 0, 0, 0) if not self._is_off \ - else (self._track_color_disabled if self.disabled \ - else (self._track_color_active \ - if self.active else self._track_color_normal)) + (0, 0, 0, 0) if not self._is_off else \ + ( \ + self.track_color_disabled \ + if self.disabled and self.track_color_disabled else \ + ( \ + self.theme_cls.disabled_hint_text_color \ + if app.theme_cls.theme_style == "Light" else \ + get_color_from_hex(colors["Gray"]["800"]) \ + ) \ + ) Line: width: 2 circle: @@ -41,55 +71,86 @@ Color: rgba: (0, 0, 0, 0) if self._is_off \ - else (self.color if not self.disabled \ - else self._track_color_disabled) + else \ + ( \ + ( \ + self.color if self.color else \ + app.theme_cls.primary_color \ + ) \ + if not self.disabled else \ + ( \ + self.track_color_disabled \ + if self.track_color_disabled else \ + ( \ + (0, 0, 0, .26) \ + if app.theme_cls.theme_style == "Light" else (1, 1, 1, .3) \ + ) \ + ) \ + ) Rectangle: size: ((self.width - self.padding * 2) * self.value_normalized, sp(4)) \ - if root.orientation == "horizontal" \ - else \ + if root.orientation == "horizontal" else \ (sp(4), (self.height - self.padding * 2) * self.value_normalized) pos: (self.x + self.padding, self.center_y - dp(4)) \ - if self.orientation == "horizontal" \ - else (self.center_x - dp(4), self.y + self.padding) + if self.orientation == "horizontal" else \ + (self.center_x - dp(4), self.y + self.padding) Thumb: id: thumb size_hint: None, None size: - (dp(12), dp(12)) \ - if root.disabled \ - else \ - ((dp(24), dp(24)) \ - if root.active \ - else \ + (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) \ + if root.active else \ (dp(16), dp(16))) pos: (root.value_pos[0] - dp(8), root.center_y - thumb.height / 2 - dp(2)) \ if root.orientation == "horizontal" \ else (root.center_x - thumb.width / 2 - dp(2), \ root.value_pos[1] - dp(8)) - color: - (0, 0, 0, 0) if root._is_off else (root._track_color_disabled \ - if root.disabled else root.color) - elevation: 0 if root._is_off else (4 if root.active else 2) + md_bg_color: + (0, 0, 0, 0) if root._is_off else \ + ( \ + ( \ + root.thumb_color_disabled \ + if root.thumb_color_disabled else \ + get_color_from_hex(colors["Gray"]["800"]) \ + ) \ + if root.disabled else \ + ( \ + (root.thumb_color_active \ + if root.thumb_color_active else \ + root.theme_cls.primary_color \ + ) \ + if root.active else \ + ( \ + root.thumb_color_inactive \ + if root.thumb_color_inactive else \ + root.theme_cls.primary_color \ + ) \ + ) \ + ) + elevation: 0 if root._is_off else (3 if root.active else 1) HintBoxContainer: id: hint_box size_hint: None, None - md_bg_color: root.hint_bg_color - elevation: 0 + md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0] + elevation: 1.5 opacity: 1 if root.active else 0 radius: root.hint_radius padding: "6dp", "6dp", "6dp", "8dp" + shadow_color: + ([0, 0, 0, 0.6] if root.hint_bg_color else [0, 0, 0, 0]) \ + if root.active else \ + [0, 0, 0, 0] size: lbl_value.width + self.padding[0] * 2, \ lbl_value.height + self.padding[0] pos: (root.value_pos[0] - dp(9), root.center_y - hint_box.height / 2 + dp(30)) \ - if root.orientation == "horizontal" \ - else \ + if root.orientation == "horizontal" else \ (root.center_x - hint_box.width / 2 + dp(30), root.value_pos[1] - dp(8)) MDLabel: @@ -101,16 +162,8 @@ adaptive_size: True pos_hint: {"center_x": .5, "center_y": .5} text_color: - ( \ - root.color \ - if root.active \ - else (0, 0, 0, 0) \ - ) \ - if not root.hint_text_color \ - else \ - root.hint_text_color + app.theme_cls.primary_color \ + if not root.hint_text_color else root.hint_text_color text: str(root.value) \ - if isinstance(root.step, float) \ - else \ - str(int(root.value)) + if isinstance(root.step, float) else str(int(root.value)) diff --git a/sbapp/kivymd/uix/slider/slider.py b/sbapp/kivymd/uix/slider/slider.py index 5611d3e..152a602 100644 --- a/sbapp/kivymd/uix/slider/slider.py +++ b/sbapp/kivymd/uix/slider/slider.py @@ -10,70 +10,13 @@ Components/Slider .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider.png :align: center - -With value hint ---------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen - - MDSlider: - min: 0 - max: 100 - value: 40 - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-1.gif - :align: center - -Without value hint ------------------- - -.. code-block:: kv - - MDSlider: - min: 0 - max: 100 - value: 40 - hint: False - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-2.gif - :align: center - -Without custom color --------------------- - -.. code-block:: kv - - MDSlider: - min: 0 - max: 100 - value: 40 - hint: False - color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-3.png - :align: center """ __all__ = ("MDSlider",) import os +from kivy.clock import Clock from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( @@ -83,10 +26,8 @@ from kivy.properties import ( VariableListProperty, ) from kivy.uix.slider import Slider -from kivy.utils import get_color_from_hex from kivymd import uix_path -from kivymd.color_definitions import colors from kivymd.theming import ThemableBehavior with open( @@ -96,6 +37,11 @@ with open( class MDSlider(ThemableBehavior, Slider): + """ + Class for creating a Slider widget. See in the + :class:`~kivy.uix.slider.Slider` class documentation. + """ + active = BooleanProperty(False) """ If the slider is clicked. @@ -104,17 +50,50 @@ class MDSlider(ThemableBehavior, Slider): and defaults to `False`. """ + color = ColorProperty(None) + """ + Color slider. + + .. code-block:: kv + + MDSlider + color: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-color.png + :align: center + + :attr:`color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + hint = BooleanProperty(True) """ If True, then the current value is displayed above the slider. + .. code-block:: kv + + MDSlider + hint: True + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint.png + :align: center + :attr:`hint` is an :class:`~kivy.properties.BooleanProperty` and defaults to `True`. """ - hint_bg_color = ColorProperty([0, 0, 0, 0]) + hint_bg_color = ColorProperty(None) """ - Hint rectangle color in ``rgba`` format. + Hint rectangle color in (r.g.b.a) format. + + .. code-block:: kv + + MDSlider + hint: True + hint_bg_color: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-bg-color.png + :align: center :attr:`hint_bg_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. @@ -122,7 +101,17 @@ class MDSlider(ThemableBehavior, Slider): hint_text_color = ColorProperty(None) """ - Hint text color in ``rgba`` format. + Hint text color in (r.g.b.a) format. + + .. code-block:: kv + + MDSlider + hint: True + hint_bg_color: "red" + hint_text_color: "white" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-text-color.png + :align: center :attr:`hint_text_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -132,10 +121,132 @@ class MDSlider(ThemableBehavior, Slider): """ Hint radius. + .. code-block:: kv + + MDSlider + hint: True + hint_bg_color: "red" + hint_text_color: "white" + hint_radius: [6, 0, 6, 0] + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-radius.png + :align: center + :attr:`hint_radius` is an :class:`~kivy.properties.VariableListProperty` and defaults to `[dp(4), dp(4), dp(4), dp(4)]`. """ + thumb_color_active = ColorProperty(None) + """ + The color of the thumb when the slider is active. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + thumb_color_active: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-active.png + :align: center + + :attr:`thumb_color_active` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + thumb_color_inactive = ColorProperty(None) + """ + The color of the thumb when the slider is inactive. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + thumb_color_inactive: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-inactive.png + :align: center + + :attr:`thumb_color_inactive` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + thumb_color_disabled = ColorProperty(None) + """ + The color of the thumb when the slider is in the disabled state. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + value: 55 + disabled: True + thumb_color_disabled: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-disabled.png + :align: center + + :attr:`thumb_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_active = ColorProperty(None) + """ + The color of the track when the slider is active. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + track_color_active: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-active.png + :align: center + + :attr:`track_color_active` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_inactive = ColorProperty(None) + """ + The color of the track when the slider is inactive. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + track_color_inactive: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-inactive.png + :align: center + + :attr:`track_color_inactive` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + + track_color_disabled = ColorProperty(None) + """ + The color of the track when the slider is in the disabled state. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDSlider + disabled: True + track_color_disabled: "red" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-disabled.png + :align: center + + :attr:`track_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and default to `None`. + """ + show_off = BooleanProperty(True) """ Show the `'off'` ring when set to minimum value. @@ -144,40 +255,29 @@ class MDSlider(ThemableBehavior, Slider): and defaults to `True`. """ - color = ColorProperty([0, 0, 0, 0]) - """ - Color slider in ``rgba`` format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _track_color_active = ColorProperty([0, 0, 0, 0]) - _track_color_normal = ColorProperty([0, 0, 0, 0]) - _track_color_disabled = ColorProperty([0, 0, 0, 0]) _thumb_pos = ListProperty([0, 0]) - _thumb_color_disabled = ColorProperty( - get_color_from_hex(colors["Gray"]["400"]) - ) - # Internal state of ring + # Internal state of ring. _is_off = BooleanProperty(False) - # Internal adjustment to reposition sliders for ring + # Internal adjustment to reposition sliders for ring. _offset = ListProperty((0, 0)) def __init__(self, **kwargs): super().__init__(**kwargs) - self.theme_cls.bind( - theme_style=self._set_colors, - primary_color=self._set_colors, - primary_palette=self._set_colors, - ) - self._set_colors() + Clock.schedule_once(self.set_thumb_icon) - def on_hint(self, instance, value): - if not value: - self.remove_widget(self.ids.hint_box) + def set_thumb_icon(self, *args) -> None: + self.ids.thumb.ids.icon.icon = "blank" - def on_value_normalized(self, *args): + def on_hint(self, instance, value) -> None: + def on_hint(*args): + if not value: + self.remove_widget(self.ids.hint_box) + + # Schedule using for declarative style. + # Otherwise get AttributeError exception. + Clock.schedule_once(on_hint) + + def on_value_normalized(self, *args) -> None: """ When the ``value == min`` set it to `'off'` state and make slider a ring. @@ -185,13 +285,13 @@ class MDSlider(ThemableBehavior, Slider): self._update_is_off() - def on_show_off(self, *args): + def on_show_off(self, *args) -> None: self._update_is_off() - def on__is_off(self, *args): + def on__is_off(self, *args) -> None: self._update_offset() - def on_active(self, *args): + def on_active(self, *args) -> None: self._update_offset() def on_touch_down(self, touch): @@ -213,26 +313,3 @@ class MDSlider(ThemableBehavior, Slider): def _update_is_off(self): self._is_off = self.show_off and (self.value_normalized == 0) - - def _set_colors(self, *args): - if self.theme_cls.theme_style == "Dark": - self._track_color_normal = get_color_from_hex("FFFFFF") - self._track_color_normal[3] = 0.3 - self._track_color_active = self._track_color_normal - self._track_color_disabled = self._track_color_normal - if self.color == [0, 0, 0, 0]: - self.color = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] - ) - self.thumb_color_disabled = get_color_from_hex( - colors["Gray"]["800"] - ) - else: - self._track_color_normal = get_color_from_hex("000000") - self._track_color_normal[3] = 0.26 - self._track_color_active = get_color_from_hex("000000") - self._track_color_active[3] = 0.38 - self._track_color_disabled = get_color_from_hex("000000") - self._track_color_disabled[3] = 0.26 - if self.color == [0, 0, 0, 0]: - self.color = self.theme_cls.primary_color diff --git a/sbapp/kivymd/uix/sliverappbar/sliverappbar.kv b/sbapp/kivymd/uix/sliverappbar/sliverappbar.kv index fd7fe7f..6757d18 100644 --- a/sbapp/kivymd/uix/sliverappbar/sliverappbar.kv +++ b/sbapp/kivymd/uix/sliverappbar/sliverappbar.kv @@ -13,7 +13,7 @@ rgba: root.background_color \ if root.background_color else \ - get_color_from_hex(root.theme_cls.primary_color) + root.theme_cls.primary_color a: root._opacity Rectangle: pos: self.pos diff --git a/sbapp/kivymd/uix/sliverappbar/sliverappbar.py b/sbapp/kivymd/uix/sliverappbar/sliverappbar.py index 4591741..6316315 100644 --- a/sbapp/kivymd/uix/sliverappbar/sliverappbar.py +++ b/sbapp/kivymd/uix/sliverappbar/sliverappbar.py @@ -38,8 +38,8 @@ Example from kivy.lang.builder import Builder + from kivymd.app import MDApp from kivymd.uix.card import MDCard - from kivymd.uix.behaviors import RoundedRectangularElevationBehavior KV = ''' @@ -47,7 +47,6 @@ Example height: "86dp" padding: "4dp" radius: 12 - elevation: 4 FitImage: source: "avatar.jpg" @@ -95,8 +94,10 @@ Example ''' - class CardItem(MDCard, RoundedRectangularElevationBehavior): - pass + class CardItem(MDCard): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.elevation = 3 class Example(MDApp): @@ -167,7 +168,7 @@ class MDSliverAppbarHeader(MDBoxLayout): pass -class MDSliverAppbar(ThemableBehavior, MDBoxLayout): +class MDSliverAppbar(MDBoxLayout, ThemableBehavior): """ MDSliverAppbar class. See module documentation for more information. @@ -192,7 +193,6 @@ class MDSliverAppbar(ThemableBehavior, MDBoxLayout): from kivymd.uix.card import MDCard from kivymd.uix.toolbar import MDTopAppBar - from kivymd.uix.behaviors import RoundedRectangularElevationBehavior KV = ''' #:import SliverToolbar __main__.SliverToolbar @@ -203,7 +203,6 @@ class MDSliverAppbar(ThemableBehavior, MDBoxLayout): height: "86dp" padding: "4dp" radius: 12 - elevation: 4 FitImage: source: "avatar.jpg" @@ -252,13 +251,16 @@ class MDSliverAppbar(ThemableBehavior, MDBoxLayout): ''' - class CardItem(MDCard, RoundedRectangularElevationBehavior): - pass + class CardItem(MDCard): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.elevation = 3 class SliverToolbar(MDTopAppBar): def __init__(self, **kwargs): super().__init__(**kwargs) + self.shadow_color = (0, 0, 0, 0) self.type_height = "medium" self.headline_text = "Headline medium" self.left_action_items = [["arrow-left", lambda x: x]] @@ -383,8 +385,8 @@ class MDSliverAppbar(ThemableBehavior, MDBoxLayout): _scroll_was_moving = BooleanProperty(False) _last_scroll_y_pos = 0.0 - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_scroll_content") def on_scroll_content( @@ -412,21 +414,27 @@ class MDSliverAppbar(ThemableBehavior, MDBoxLayout): ) -> None: """Called when a value is set to the :attr:`toolbar_cls` parameter.""" - # If an MDTopAppBar object is already in use, delete it - # before adding a new MDTopAppBar object. - for widget in self.ids.float_box.children: - if issubclass(widget.__class__, MDTopAppBar): - self.ids.float_box.remove_widget(widget) + def on_toolbar_cls(*args): + # If an MDTopAppBar object is already in use, delete it + # before adding a new MDTopAppBar object. + for widget in self.ids.float_box.children: + if issubclass(widget.__class__, MDTopAppBar): + self.ids.float_box.remove_widget(widget) - # Adding a custom MDTopAppBar object. - if issubclass(instance_toolbar_cls.__class__, MDTopAppBar): - instance_toolbar_cls.pos_hint = {"top": 1} - self.ids.float_box.add_widget(instance_toolbar_cls) - else: - raise MDSliverAppbarException( - "The `toolbar_cls` parameter must be an object of the " - "`kivymd.uix.toolbar.MDTopAppBar class`" - ) + # Adding a custom MDTopAppBar object. + if issubclass(instance_toolbar_cls.__class__, MDTopAppBar): + instance_toolbar_cls.pos_hint = {"top": 1} + instance_toolbar_cls.elevation = 0 + self.ids.float_box.add_widget(instance_toolbar_cls) + else: + raise MDSliverAppbarException( + "The `toolbar_cls` parameter must be an object of the " + "`kivymd.uix.toolbar.MDTopAppBar class`" + ) + + # Schedule using for declarative style. + # Otherwise get AttributeError exception. + Clock.schedule_once(on_toolbar_cls) def on_vbar(self) -> None: if not self.background_color: diff --git a/sbapp/kivymd/uix/snackbar/snackbar.kv b/sbapp/kivymd/uix/snackbar/snackbar.kv index 4dcef8b..557ceda 100644 --- a/sbapp/kivymd/uix/snackbar/snackbar.kv +++ b/sbapp/kivymd/uix/snackbar/snackbar.kv @@ -8,7 +8,7 @@ padding: "10dp", "10dp", "10dp", "10dp" md_bg_color: "323232" if not root.bg_color else root.bg_color radius: root.radius - elevation: 11 if root.padding else 0 + elevation: 4 if root.padding else 0 canvas: Color: diff --git a/sbapp/kivymd/uix/snackbar/snackbar.py b/sbapp/kivymd/uix/snackbar/snackbar.py index 5154441..c4fbdc9 100755 --- a/sbapp/kivymd/uix/snackbar/snackbar.py +++ b/sbapp/kivymd/uix/snackbar/snackbar.py @@ -25,7 +25,7 @@ Usage #:import Snackbar kivymd.uix.snackbar.Snackbar - Screen: + MDScreen: MDRaisedButton: text: "Create simple snackbar" @@ -149,7 +149,7 @@ Custom usage KV = ''' - Screen: + MDScreen: MDFloatingActionButton: id: button @@ -227,7 +227,7 @@ Custom Snackbar pos_hint: {'center_y': .5} - Screen: + MDScreen: MDRaisedButton: text: "SHOW" @@ -284,7 +284,6 @@ from kivy.properties import ( ) from kivymd import uix_path -from kivymd.uix.behaviors import FakeRectangularElevationBehavior from kivymd.uix.button import BaseButton from kivymd.uix.card import MDCard @@ -294,7 +293,7 @@ with open( Builder.load_string(kv_file.read()) -class BaseSnackbar(MDCard, FakeRectangularElevationBehavior): +class BaseSnackbar(MDCard): """ :Events: :attr:`on_open` diff --git a/sbapp/kivymd/uix/stacklayout.py b/sbapp/kivymd/uix/stacklayout.py index 4782302..8988e8b 100644 --- a/sbapp/kivymd/uix/stacklayout.py +++ b/sbapp/kivymd/uix/stacklayout.py @@ -8,7 +8,7 @@ with some widget properties. For example: StackLayout ----------- -.. code-block:: +.. code-block:: kv StackLayout: size_hint_y: None @@ -24,7 +24,7 @@ StackLayout MDStackLayout ------------- -.. code-block:: +.. code-block:: kv MDStackLayout: adaptive_height: True @@ -83,10 +83,16 @@ Equivalent size: self.minimum_size """ +__all__ = ("MDStackLayout",) + from kivy.uix.stacklayout import StackLayout from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDStackLayout(StackLayout, MDAdaptiveWidget): - pass +class MDStackLayout(DeclarativeBehavior, StackLayout, MDAdaptiveWidget): + """ + Stack layout class. For more information, see in the + :class:`~kivy.uix.stacklayout.StackLayout` class documentation. + """ diff --git a/sbapp/kivymd/uix/swiper/swiper.py b/sbapp/kivymd/uix/swiper/swiper.py index 4fd620b..b89e2e5 100644 --- a/sbapp/kivymd/uix/swiper/swiper.py +++ b/sbapp/kivymd/uix/swiper/swiper.py @@ -1,6 +1,6 @@ """ -Components/MDSwiper -=================== +Components/Swiper +================= .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-preview.gif :align: center @@ -38,7 +38,7 @@ Example MDTopAppBar: id: toolbar title: "MDSwiper" - elevation: 10 + elevation: 4 pos_hint: {"top": 1} MDSwiper: @@ -142,7 +142,7 @@ Example MDTopAppBar: id: toolbar title: "MDSwiper" - elevation: 10 + elevation: 4 pos_hint: {"top": 1} MDSwiper: @@ -203,7 +203,6 @@ from kivy.animation import Animation from kivy.clock import Clock from kivy.core.window import Window from kivy.effects.dampedscroll import DampedScrollEffect -from kivy.event import EventDispatcher from kivy.lang.builder import Builder from kivy.properties import ( BooleanProperty, @@ -212,11 +211,11 @@ from kivy.properties import ( StringProperty, ) from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.scrollview import ScrollView from kivy.utils import platform from kivymd import uix_path +from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.scrollview import MDScrollView with open( os.path.join(uix_path, "swiper", "swiper.kv"), encoding="utf-8" @@ -249,7 +248,7 @@ class _ItemsBox(AnchorLayout): ] -class MDSwiperItem(BoxLayout): +class MDSwiperItem(MDBoxLayout): """ :class:`MDSwiperItem` is a :class:`BoxLayout` but it's size is adjusted automatically. @@ -258,8 +257,8 @@ class MDSwiperItem(BoxLayout): _root = ObjectProperty() _selected = False - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) Clock.schedule_once(self._set_size) Window.bind(on_resize=self._set_size) @@ -293,7 +292,7 @@ class MDSwiperItem(BoxLayout): anim.start(self) -class MDSwiper(ScrollView, EventDispatcher): +class MDSwiper(MDScrollView): items_spacing = NumericProperty("20dp") """ The space between each :class:`MDSwiperItem`. @@ -373,8 +372,8 @@ class MDSwiper(ScrollView, EventDispatcher): "on_swipe_right", ) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_swipe") self.register_event_type("on_pre_swipe") self.register_event_type("on_overswipe_right") diff --git a/sbapp/kivymd/uix/tab/tab.kv b/sbapp/kivymd/uix/tab/tab.kv index 824f82e..f219bc1 100644 --- a/sbapp/kivymd/uix/tab/tab.kv +++ b/sbapp/kivymd/uix/tab/tab.kv @@ -79,6 +79,10 @@ layout: layout size_hint: 1, None elevation: root.elevation + radius: root.radius + shadow_offset: root.shadow_offset + shadow_color: root.shadow_color + shadow_softness: root.shadow_softness height: root.tab_bar_height md_bg_color: self.theme_cls.primary_color \ diff --git a/sbapp/kivymd/uix/tab/tab.py b/sbapp/kivymd/uix/tab/tab.py index b78ffd6..816711e 100755 --- a/sbapp/kivymd/uix/tab/tab.py +++ b/sbapp/kivymd/uix/tab/tab.py @@ -25,12 +25,10 @@ content for the tab. class Tab(MDFloatLayout, MDTabsBase): '''Class implementing content for a tab.''' - content_text = StringProperty("") .. code-block:: kv - content_text MDLabel: text: root.content_text @@ -57,71 +55,139 @@ All tabs must be contained inside a :class:`~MDTabs` widget: Example with tab icon --------------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" + from kivymd.app import MDApp + from kivymd.uix.tab import MDTabsBase + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.icon_definitions import md_icons - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) + KV = ''' + MDBoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Example Tabs" + + MDTabs: + id: tabs + on_tab_switch: app.on_tab_switch(*args) - + - MDIconButton: - id: icon - icon: root.icon - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for tab_name in self.icons: - self.root.ids.tabs.add_widget(Tab(icon=tab_name)) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): + MDIconButton: + id: icon + icon: root.icon + icon_size: "48sp" + pos_hint: {"center_x": .5, "center_y": .5} ''' - Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - # get the tab icon. - count_icon = instance_tab.icon - # print it on shell/bash. - print(f"Welcome to {count_icon}' tab'") - Example().run() + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + class Example(MDApp): + icons = list(md_icons.keys())[15:30] + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + def on_start(self): + for tab_name in self.icons: + self.root.ids.tabs.add_widget(Tab(icon=tab_name)) + + def on_tab_switch( + self, instance_tabs, instance_tab, instance_tab_label, tab_text + ): + ''' + Called when switching tabs. + + :type instance_tabs: ; + :param instance_tab: <__main__.Tab object>; + :param instance_tab_label: ; + :param tab_text: text or name icon of tab; + ''' + + count_icon = instance_tab.icon # get the tab icon + print(f"Welcome to {count_icon}' tab'") + + + Example().run() + + .. tab:: Declarative python styles + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.icon_definitions import md_icons + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + + + class Example(MDApp): + icons = list(md_icons.keys())[15:30] + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch) + + for tab_name in self.icons: + self.root.ids.tabs.add_widget( + Tab( + MDIconButton( + icon=tab_name, + icon_size="48sp", + pos_hint={"center_x": .5, "center_y": .5}, + ), + icon=tab_name, + ) + ) + + def on_tab_switch( + self, instance_tabs, instance_tab, instance_tab_label, tab_text + ): + ''' + Called when switching tabs. + + :type instance_tabs: ; + :param instance_tab: <__main__.Tab object>; + :param instance_tab_label: ; + :param tab_text: text or name icon of tab; + ''' + + count_icon = instance_tab.icon # get the tab icon + print(f"Welcome to {count_icon}' tab'") + + + Example().run() + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example.gif :align: center @@ -138,62 +204,123 @@ Example with tab text if the tab has no icon, title or tab_label_text, the class will raise a ValueError. -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" + from kivymd.app import MDApp + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) + KV = ''' + MDBoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Example Tabs" + + MDTabs: + id: tabs + on_tab_switch: app.on_tab_switch(*args) - + - MDLabel: - id: label - text: "Tab 0" - halign: "center" - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(20): - self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}")) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - '''Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; + MDLabel: + id: label + text: "Tab 0" + halign: "center" ''' - instance_tab.ids.label.text = tab_text + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' - Example().run() + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + def on_start(self): + for i in range(20): + self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}")) + + def on_tab_switch( + self, instance_tabs, instance_tab, instance_tab_label, tab_text + ): + '''Called when switching tabs. + + :type instance_tabs: ; + :param instance_tab: <__main__.Tab object>; + :param instance_tab_label: ; + :param tab_text: text or name icon of tab; + ''' + + instance_tab.ids.label.text = tab_text + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.label import MDLabel + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch) + for i in range(20): + self.root.ids.tabs.add_widget( + Tab( + MDLabel(id="label", text="Tab 0", halign="center"), + title=f"Tab {i}", + ) + ) + + def on_tab_switch( + self, instance_tabs, instance_tab, instance_tab_label, tab_text + ): + ''' + Called when switching tabs. + + :type instance_tabs: ; + :param instance_tab: <__main__.Tab object>; + :param instance_tab_label: ; + :param tab_text: text or name icon of tab; + ''' + + instance_tab.ids.label.text = tab_text + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-text.gif :align: center @@ -201,119 +328,240 @@ Example with tab text Example with tab icon and text ------------------------------ -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" + from kivymd.app import MDApp + from kivymd.uix.tab import MDTabsBase + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.icon_definitions import md_icons - MDTabs: - id: tabs - ''' + KV = ''' + MDBoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Example Tabs" + + MDTabs: + id: tabs + ''' - class Tab(MDFloatLayout, MDTabsBase): - pass + class Tab(MDFloatLayout, MDTabsBase): + pass - class Example(MDApp): - def build(self): - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def on_start(self): - for name_tab in list(md_icons.keys())[15:30]: - self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) + def on_start(self): + for name_tab in list(md_icons.keys())[15:30]: + self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) - Example().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.png + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.icon_definitions import md_icons + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDFloatLayout, MDTabsBase): + pass + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + for name_tab in list(md_icons.keys())[15:30]: + self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.gif :align: center Dynamic tab management ---------------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder - from kivy.uix.scrollview import ScrollView + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - - - - - MDList: + from kivymd.uix.scrollview import MDScrollView + from kivymd.app import MDApp + from kivymd.uix.tab import MDTabsBase + KV = ''' MDBoxLayout: - adaptive_height: True + orientation: "vertical" - MDFlatButton: - text: "ADD TAB" - on_release: app.add_tab() + MDTopAppBar: + title: "Example Tabs" - MDFlatButton: - text: "REMOVE LAST TAB" - on_release: app.remove_tab() - - MDFlatButton: - text: "GET TAB LIST" - on_release: app.get_tab_list() - ''' + MDTabs: + id: tabs - class Tab(ScrollView, MDTabsBase): - '''Class implementing content for a tab.''' + + + MDList: + + MDBoxLayout: + adaptive_height: True + + MDFlatButton: + text: "ADD TAB" + on_release: app.add_tab() + + MDFlatButton: + text: "REMOVE LAST TAB" + on_release: app.remove_tab() + + MDFlatButton: + text: "GET TAB LIST" + on_release: app.get_tab_list() + ''' - class Example(MDApp): - index = 0 - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - self.add_tab() - - def get_tab_list(self): - '''Prints a list of tab objects.''' - - print(self.root.ids.tabs.get_tab_list()) - - def add_tab(self): - self.index += 1 - self.root.ids.tabs.add_widget(Tab(text=f"{self.index} tab")) - - def remove_tab(self): - if self.index > 1: - self.index -= 1 - self.root.ids.tabs.remove_widget( - self.root.ids.tabs.get_tab_list()[-1] - ) + class Tab(MDScrollView, MDTabsBase): + '''Class implementing content for a tab.''' - Example().run() + class Example(MDApp): + index = 0 + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + def on_start(self): + self.add_tab() + + def get_tab_list(self): + '''Prints a list of tab objects.''' + + print(self.root.ids.tabs.get_tab_list()) + + def add_tab(self): + self.index += 1 + self.root.ids.tabs.add_widget(Tab(title=f"{self.index} tab")) + + def remove_tab(self): + if self.index > 1: + self.index -= 1 + self.root.ids.tabs.remove_widget( + self.root.ids.tabs.get_tab_list()[-1] + ) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.uix.button import MDFlatButton + from kivymd.uix.list import MDList + from kivymd.uix.scrollview import MDScrollView + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDScrollView, MDTabsBase): + '''Class implementing content for a tab.''' + + + class Example(MDApp): + index = 0 + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + self.add_tab() + + def get_tab_list(self, *args): + '''Prints a list of tab objects.''' + + print(self.root.ids.tabs.get_tab_list()) + + def add_tab(self, *args): + self.index += 1 + self.root.ids.tabs.add_widget( + Tab( + MDList( + MDBoxLayout( + MDFlatButton( + text="ADD TAB", + on_release=self.add_tab, + ), + MDFlatButton( + text="REMOVE LAST TAB", + on_release=self.remove_tab, + ), + MDFlatButton( + text="GET TAB LIST", + on_release=self.get_tab_list, + ), + adaptive_height=True, + ), + ), + title=f"{self.index} tab", + ) + ) + + def remove_tab(self, *args): + if self.index > 1: + self.index -= 1 + self.root.ids.tabs.remove_widget( + self.root.ids.tabs.get_tab_list()[-1] + ) + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-dynamic-managmant.gif :align: center @@ -324,83 +572,166 @@ Use on_ref_press method You can use markup for the text of the tabs and use the ``on_ref_press`` method accordingly: -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.font_definitions import fonts - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" + from kivymd.app import MDApp + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.font_definitions import fonts + from kivymd.uix.tab import MDTabsBase + from kivymd.icon_definitions import md_icons - MDTabs: - id: tabs - on_ref_press: app.on_ref_press(*args) + KV = ''' + MDBoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Example Tabs" + + MDTabs: + id: tabs + on_ref_press: app.on_ref_press(*args) - + - MDIconButton: - id: icon - icon: app.icons[0] - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' + MDIconButton: + id: icon + icon: app.icons[0] + icon_size: "48sp" + pos_hint: {"center_x": .5, "center_y": .5} + ''' - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' - class Example(MDApp): - icons = list(md_icons.keys())[15:30] + class Example(MDApp): + icons = list(md_icons.keys())[15:30] - def build(self): - return Builder.load_string(KV) + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) - def on_start(self): - for name_tab in self.icons: - self.root.ids.tabs.add_widget( - Tab( - text=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref] {name_tab}" + def on_start(self): + for name_tab in self.icons: + self.root.ids.tabs.add_widget( + Tab( + title=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref] {name_tab}" + ) + ) + + def on_ref_press( + self, + instance_tabs, + instance_tab_label, + instance_tab, + instance_tab_bar, + instance_carousel, + ): + ''' + The method will be called when the ``on_ref_press`` event + occurs when you, for example, use markup text for tabs. + + :param instance_tabs: + :param instance_tab_label: + :param instance_tab: <__main__.Tab object> + :param instance_tab_bar: + :param instance_carousel: + ''' + + # Removes a tab by clicking on the close icon on the left. + for instance_tab in instance_carousel.slides: + if instance_tab.title == instance_tab_label.text: + instance_tabs.remove_widget(instance_tab_label) + break + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.font_definitions import fonts + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.icon_definitions import md_icons + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + + + class Example(MDApp): + icons = list(md_icons.keys())[15:30] + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) ) - ) - def on_ref_press( - self, - instance_tabs, - instance_tab_label, - instance_tab, - instance_tab_bar, - instance_carousel, - ): - ''' - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. + def on_start(self): + self.root.ids.tabs.bind(on_ref_press=self.on_ref_press) + for name_tab in self.icons: + self.root.ids.tabs.add_widget( + Tab( + MDIconButton( + icon=self.icons[0], + icon_size="48sp", + pos_hint={"center_x": .5, "center_y": .5} + ), + title=( + f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]" + f"{md_icons['close']}[/font][/ref] {name_tab}" + ), + ) + ) - :param instance_tabs: - :param instance_tab_label: - :param instance_tab: <__main__.Tab object> - :param instance_tab_bar: - :param instance_carousel: - ''' + def on_ref_press( + self, + instance_tabs, + instance_tab_label, + instance_tab, + instance_tab_bar, + instance_carousel, + ): + ''' + The method will be called when the ``on_ref_press`` event + occurs when you, for example, use markup text for tabs. - # Removes a tab by clicking on the close icon on the left. - for instance_tab in instance_carousel.slides: - if instance_tab.text == instance_tab_label.text: - instance_tabs.remove_widget(instance_tab_label) - break + :param instance_tabs: + :param instance_tab_label: + :param instance_tab: <__main__.Tab object> + :param instance_tab_bar: + :param instance_carousel: + ''' + + # Removes a tab by clicking on the close icon on the left. + for instance_tab in instance_carousel.slides: + if instance_tab.title == instance_tab_label.text: + instance_tabs.remove_widget(instance_tab_label) + break - Example().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-on-ref-press.gif :align: center @@ -408,88 +739,182 @@ method accordingly: Switching the tab by name ------------------------- -.. code-block:: python +.. tabs:: - from kivy.lang import Builder + .. tab:: Declarative KV and imperative python styles - from kivymd.app import MDApp - from kivymd.icon_definitions import md_icons - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase + .. code-block:: python - KV = ''' - MDBoxLayout: - orientation: "vertical" + from kivy.lang import Builder - MDTopAppBar: - title: "Example Tabs" + from kivymd.app import MDApp + from kivymd.icon_definitions import md_icons + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase - MDTabs: - id: tabs + KV = ''' + MDBoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Example Tabs" + + MDTabs: + id: tabs - + - MDBoxLayout: - orientation: "vertical" - pos_hint: {"center_x": .5, "center_y": .5} - size_hint: None, None - spacing: dp(48) + MDBoxLayout: + orientation: "vertical" + pos_hint: {"center_x": .5, "center_y": .5} + adaptive_size: True + spacing: dp(48) - MDIconButton: - id: icon - icon: "arrow-right" - user_font_size: "48sp" - on_release: app.switch_tab_by_name() + MDIconButton: + id: icon + icon: "arrow-right" + icon_size: "48sp" + on_release: app.switch_tab_by_name() - MDIconButton: - id: icon2 - icon: "page-next" - user_font_size: "48sp" - on_release: app.switch_tab_by_object() - ''' + MDIconButton: + id: icon2 + icon: "page-next" + icon_size: "48sp" + on_release: app.switch_tab_by_object() + ''' - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' - class Example(MDApp): - icons = list(md_icons.keys())[15:30] + class Example(MDApp): + icons = list(md_icons.keys())[15:30] - def build(self): - self.iter_list_names = iter(list(self.icons)) - return Builder.load_string(KV) + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + self.iter_list_names = iter(list(self.icons)) + return Builder.load_string(KV) - def on_start(self): - for name_tab in list(self.icons): - self.root.ids.tabs.add_widget(Tab(tab_label_text=name_tab)) - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) + def on_start(self): + for name_tab in list(self.icons): + self.root.ids.tabs.add_widget(Tab(tab_label_text=name_tab)) + self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - def switch_tab_by_object(self): - try: - x = next(self.iter_list_objects) - print(f"Switch slide by object, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # reset the iterator an begin again. - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - self.switch_tab_by_object() + def switch_tab_by_object(self): + try: + x = next(self.iter_list_objects) + print(f"Switch slide by object, next element to show: [{x}]") + self.root.ids.tabs.switch_tab(x) + except StopIteration: + # reset the iterator an begin again. + self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) + self.switch_tab_by_object() - def switch_tab_by_name(self): - '''Switching the tab by name.''' + def switch_tab_by_name(self): + '''Switching the tab by name.''' - try: - x = next(self.iter_list_names) - print(f"Switch slide by name, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # Reset the iterator an begin again. - self.iter_list_names = iter(list(self.icons)) - self.switch_tab_by_name() + try: + x = next(self.iter_list_names) + print(f"Switch slide by name, next element to show: [{x}]") + self.root.ids.tabs.switch_tab(x) + except StopIteration: + # Reset the iterator an begin again. + self.iter_list_names = iter(list(self.icons)) + self.switch_tab_by_name() - Example().run() + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.icon_definitions import md_icons + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.button import MDIconButton + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase, MDTabs + from kivymd.uix.toolbar import MDTopAppBar + + + class Tab(MDFloatLayout, MDTabsBase): + '''Class implementing content for a tab.''' + + + class Example(MDApp): + icons = list(md_icons.keys())[15:30] + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + self.iter_list_names = iter(list(self.icons)) + return ( + MDBoxLayout( + MDTopAppBar(title="Example Tabs"), + MDTabs(id="tabs"), + orientation="vertical", + ) + ) + + def on_start(self): + for name_tab in list(self.icons): + self.root.ids.tabs.add_widget( + Tab( + MDBoxLayout( + MDIconButton( + id="icon", + icon="arrow-right", + icon_size="48sp", + on_release=self.switch_tab_by_name, + ), + MDIconButton( + id="icon2", + icon="arrow-left", + icon_size="48sp", + on_release=self.switch_tab_by_object, + ), + orientation="vertical", + pos_hint={"center_x": .5, "center_y": .5}, + adaptive_size=True, + spacing=dp(48), + ), + tab_label_text=name_tab, + ) + ) + + self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) + + def switch_tab_by_object(self, *args): + try: + x = next(self.iter_list_objects) + print(f"Switch slide by object, next element to show: [{x}]") + self.root.ids.tabs.switch_tab(x) + except StopIteration: + # reset the iterator an begin again. + self.iter_list_objects = iter( + list(self.root.ids.tabs.get_tab_list())) + self.switch_tab_by_object() + + def switch_tab_by_name(self, *args): + '''Switching the tab by name.''' + + try: + x = next(self.iter_list_names) + print(f"Switch slide by name, next element to show: [{x}]") + self.root.ids.tabs.switch_tab(x) + except StopIteration: + # Reset the iterator an begin again. + self.iter_list_names = iter(list(self.icons)) + self.switch_tab_by_name() + + + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switching-tab-by-name.gif :align: center @@ -519,7 +944,6 @@ from kivy.properties import ( from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.scrollview import ScrollView -from kivy.uix.widget import Widget from kivy.utils import boundary from kivymd import uix_path @@ -527,11 +951,12 @@ from kivymd.font_definitions import fonts, theme_font_styles from kivymd.icon_definitions import md_icons from kivymd.theming import ThemableBehavior, ThemeManager from kivymd.uix.behaviors import ( - FakeRectangularElevationBehavior, + DeclarativeBehavior, RectangularRippleBehavior, SpecificBackgroundColorBehavior, ) from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.card import MDCard from kivymd.uix.carousel import MDCarousel from kivymd.uix.label import MDLabel @@ -564,10 +989,15 @@ class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel): ) def on_release(self) -> None: - self.tab_bar.parent.dispatch("on_tab_switch", self.tab, self, self.text) - # If the label is selected load the relative tab from carousel. - if self.state == "down": - self.tab_bar.parent.carousel.load_slide(self.tab) + try: + self.tab_bar.parent.dispatch( + "on_tab_switch", self.tab, self, self.text + ) + # If the label is selected load the relative tab from carousel. + if self.state == "down": + self.tab_bar.parent.carousel.load_slide(self.tab) + except KeyError: + pass def on_texture(self, instance_tabs_label, texture: Texture) -> None: # Just save the minimum width of the label based of the content. @@ -593,7 +1023,7 @@ class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel): Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0) -class MDTabsBase(Widget): +class MDTabsBase: """ This class allow you to create a tab. You must create a new class that inherits from MDTabsBase. @@ -645,22 +1075,6 @@ class MDTabsBase(Widget): and defaults to `True`. """ - text = StringProperty(deprecated=True) - """ - This property is the actual title of the tab. - use the property :attr:`icon` and :attr:`title` to set this property - correctly. - - This property is kept public for specific and backward compatibility - purposes. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - - .. deprecated:: 1.0.0 - Use :attr:`tab_label_text` instead. - """ - tab_label_text = StringProperty() """ This property is the actual title's Label of the tab. @@ -715,14 +1129,13 @@ class MDTabsBase(Widget): This property will affect the Tab's Title Label widget. """ - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): self.tab_label = MDTabsLabel(tab=self) - super().__init__(**kwargs) + super().__init__(*args, **kwargs) self.bind( icon=self._update_text, title=self._update_text, title_icon_mode=self._update_text, - text=self.update_label_text, tab_label_text=self.update_label_text, title_is_capital=self.update_label_text, ) @@ -742,7 +1155,7 @@ class MDTabsBase(Widget): self.tab_label_text = f"[size=24sp][font={fonts[-1]['fn_regular']}]{md_icons[self.icon]}[/size][/font]" if self.title: self.tab_label_text = ( - self.text + self.tab_label_text + (" " if self.title_icon_mode == "Lead" else "\n") + self.title ) @@ -769,10 +1182,7 @@ class MDTabsBase(Widget): self.update_label_text(None, self.tab_label_text) def update_label_text(self, instance_user_tab, text_tab: str) -> None: - self.tab_label.text = self.text = self.tab_label_text - - def on_text(self, instance_user_tab, text_tab: str) -> None: - self.tab_label_text = self.text + self.tab_label.text = text_tab class MDTabsMain(MDBoxLayout): @@ -862,9 +1272,7 @@ class MDTabsScrollView(ScrollView): _update(self.effect_y, scroll_y) -class MDTabsBar( - ThemableBehavior, FakeRectangularElevationBehavior, MDBoxLayout -): +class MDTabsBar(MDCard): """ This class is just a boxlayout that contains the scroll view for tabs. It is also responsible for resizing the tab shortcut when necessary. @@ -997,7 +1405,12 @@ class MDTabsBar( self.update_indicator(widget.x, widget.width) -class MDTabs(ThemableBehavior, SpecificBackgroundColorBehavior, AnchorLayout): +class MDTabs( + DeclarativeBehavior, + ThemableBehavior, + SpecificBackgroundColorBehavior, + AnchorLayout, +): """ You can use this class to create your own tabbed panel. @@ -1135,13 +1548,43 @@ class MDTabs(ThemableBehavior, SpecificBackgroundColorBehavior, AnchorLayout): and defaults to `None`. """ + shadow_softness = NumericProperty(12) + """ + See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness` + attribute. + + .. versionadded:: 1.1.0 + + :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` + and defaults to `12`. + """ + + shadow_color = ColorProperty([0, 0, 0, 0.6]) + """ + See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color` + attribute. + + .. versionadded:: 1.1.0 + + :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0.6]`. + """ + + shadow_offset = ListProperty((0, 0)) + """ + See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset` + attribute. + + .. versionadded:: 1.1.0 + + :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` + and defaults to `[0, 0]`. + """ + elevation = NumericProperty(0) """ - Tab value elevation. - - .. seealso:: - - `Behaviors/Elevation `_ + See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation` + attribute. :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. @@ -1204,8 +1647,8 @@ class MDTabs(ThemableBehavior, SpecificBackgroundColorBehavior, AnchorLayout): and defaults to `True`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.register_event_type("on_tab_switch") self.register_event_type("on_ref_press") self.register_event_type("on_slide_progress") diff --git a/sbapp/kivymd/uix/taptargetview.py b/sbapp/kivymd/uix/taptargetview.py index 1e57ff0..735de0d 100644 --- a/sbapp/kivymd/uix/taptargetview.py +++ b/sbapp/kivymd/uix/taptargetview.py @@ -487,6 +487,8 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): _outer_radius = NumericProperty(0) _target_radius = NumericProperty(0) + __elevation = 0 + def __init__(self, **kwargs): self.ripple_max_dist = dp(90) self.on_outer_radius(self, self.outer_radius) @@ -514,6 +516,89 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): if not self.outer_circle_color: self.outer_circle_color = self.theme_cls.primary_color[:-1] + def start(self, *args): + """Starts widget opening animation.""" + + self._initialize() + self._animate_outer() + self.state = "open" + self.core_title_text.opacity = 1 + self.core_description_text.opacity = 1 + self.dispatch("on_open") + + elevation = getattr(self.widget, "elevation", None) + if elevation: + self.__elevation = elevation + self.widget.elevation = 0 + + def stop(self, *args): + """Starts widget close animation.""" + + # It needs a better implementation. + if self.anim_ripple is not None: + self.anim_ripple.unbind(on_complete=self._repeat_ripple) + self.core_title_text.opacity = 0 + self.core_description_text.opacity = 0 + anim = Animation( + d=0.15, + t="in_cubic", + **dict( + zip( + ["_outer_radius", "_target_radius", "target_ripple_radius"], + [0, 0, 0], + ) + ), + ) + anim.bind(on_complete=self._after_stop) + anim.start(self.widget) + + def on_open(self, *args): + """Called at the time of the start of the widget opening animation.""" + + def on_close(self, *args): + """Called at the time of the start of the widget closed animation.""" + + def on_draw_shadow(self, instance, value): + Logger.warning( + "The shadow adding method will be implemented in future versions" + ) + + def on_description_text(self, instance, value): + self.core_description_text.text = value + + def on_description_text_size(self, instance, value): + self.core_description_text.font_size = value + + def on_description_text_bold(self, instance, value): + self.core_description_text.bold = value + + def on_title_text(self, instance, value): + self.core_title_text.text = value + + def on_title_text_size(self, instance, value): + self.core_title_text.font_size = value + + def on_title_text_bold(self, instance, value): + self.core_title_text.bold = value + + def on_outer_radius(self, instance, value): + self._outer_radius = self.outer_radius * 2 + + def on_target_radius(self, instance, value): + self._target_radius = self.target_radius * 2 + + def on_target_touch(self): + if self.stop_on_target_touch: + self.stop() + + def on_outer_touch(self): + if self.stop_on_outer_touch: + self.stop() + + def on_outside_click(self): + if self.cancelable: + self.stop() + def _initialize(self): setattr(self.widget, "_outer_radius", 0) setattr(self.widget, "_target_radius", 0) @@ -527,7 +612,7 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): def _draw_canvas(self): _pos = self._ttv_pos() - self.widget.canvas.before.clear() + self.widget.canvas.before.remove_group("ttv_group") with self.widget.canvas.before: # Outer circle. @@ -588,34 +673,14 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): group="ttv_group", ) - def stop(self, *args): - """Starts widget close animation.""" - - # It needs a better implementation. - if self.anim_ripple is not None: - self.anim_ripple.unbind(on_complete=self._repeat_ripple) - self.core_title_text.opacity = 0 - self.core_description_text.opacity = 0 - anim = Animation( - d=0.15, - t="in_cubic", - **dict( - zip( - ["_outer_radius", "_target_radius", "target_ripple_radius"], - [0, 0, 0], - ) - ), - ) - anim.bind(on_complete=self._after_stop) - anim.start(self.widget) - def _after_stop(self, *args): self.widget.canvas.before.remove_group("ttv_group") args[0].stop_all(self.widget) - elev = getattr(self.widget, "elevation", None) - if elev: - self._fix_elev() + elevation = getattr(self.widget, "elevation", None) + if elevation: + self.widget.elevation = self.__elevation + self.dispatch("on_close") # Don't forget to unbind the function or it'll mess @@ -639,16 +704,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): ) Color(a=1) - def start(self, *args): - """Starts widget opening animation.""" - - self._initialize() - self._animate_outer() - self.state = "open" - self.core_title_text.opacity = 1 - self.core_description_text.opacity = 1 - self.dispatch("on_open") - def _animate_outer(self): anim = Animation( d=0.2, @@ -684,53 +739,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher): setattr(self.widget, "target_ripple_alpha", 1) self._animate_ripple() - def on_open(self, *args): - """Called at the time of the start of the widget opening animation.""" - - def on_close(self, *args): - """Called at the time of the start of the widget closed animation.""" - - def on_draw_shadow(self, instance, value): - Logger.warning( - "The shadow adding method will be implemented in future versions" - ) - - def on_description_text(self, instance, value): - self.core_description_text.text = value - - def on_description_text_size(self, instance, value): - self.core_description_text.font_size = value - - def on_description_text_bold(self, instance, value): - self.core_description_text.bold = value - - def on_title_text(self, instance, value): - self.core_title_text.text = value - - def on_title_text_size(self, instance, value): - self.core_title_text.font_size = value - - def on_title_text_bold(self, instance, value): - self.core_title_text.bold = value - - def on_outer_radius(self, instance, value): - self._outer_radius = self.outer_radius * 2 - - def on_target_radius(self, instance, value): - self._target_radius = self.target_radius * 2 - - def on_target_touch(self): - if self.stop_on_target_touch: - self.stop() - - def on_outer_touch(self): - if self.stop_on_outer_touch: - self.stop() - - def on_outside_click(self): - if self.cancelable: - self.stop() - def _some_func(self, wid, touch): """ This function decides which one to dispatch based on the touch diff --git a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv deleted file mode 100644 index 77b57df..0000000 --- a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv +++ /dev/null @@ -1,9 +0,0 @@ - - canvas.before: - PushMatrix - Rotate: - angle: self.rotate_value_angle - axis: tuple(self.rotate_value_axis) - origin: self.center - canvas.after: - PopMatrix diff --git a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py index d3491df..9416a5f 100644 --- a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py +++ b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py @@ -2,127 +2,31 @@ Templates/RotateWidget ====================== -.. versionadded:: 1.0.0 +.. deprecated:: 1.0.0 -Base class for controlling the rotate of the widget. - -.. note:: See `kivy.graphics.Rotate - `_ - for more information. - -Kivy ----- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.app import App - from kivy.properties import NumericProperty - from kivy.uix.button import Button - - KV = ''' - Screen: - - RotateButton: - size_hint: .5, .5 - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.change_rotate(self) - - canvas.before: - PushMatrix - Rotate: - angle: self.rotate_value_angle - axis: 0, 0, 1 - origin: self.center - canvas.after: - PopMatrix - ''' - - - class RotateButton(Button): - rotate_value_angle = NumericProperty(0) - - - class Test(App): - def build(self): - return Builder.load_string(KV) - - def change_rotate(self, instance_button: Button) -> None: - Animation(rotate_value_angle=45, d=0.3).start(instance_button) - - - Test().run() - -KivyMD ------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton - from kivymd.uix.templates import RotateWidget - - KV = ''' - MDScreen: - - RotateButton: - size_hint: .5, .5 - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.change_rotate(self) - elevation:0 - ''' - - - class RotateButton(MDRaisedButton, RotateWidget): - pass - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def change_rotate(self, instance_button: MDRaisedButton) -> None: - Animation(rotate_value_angle=45, d=0.3).start(instance_button) - - - Test().run() +.. note:: `RotateWidget` class has been deprecated. Please use + `RotateBahavior `_ + class instead. """ __all__ = ("RotateWidget",) -import os +from kivy import Logger -from kivy.lang import Builder -from kivy.properties import ListProperty, NumericProperty - -from kivymd import uix_path - -with open( - os.path.join(uix_path, "templates", "rotatewidget", "rotatewidget.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) +from kivymd.uix.behaviors import RotateBehavior -class RotateWidget: - """Base class for controlling the rotate of the widget.""" - - rotate_value_angle = NumericProperty(0) +class RotateWidget(RotateBehavior): """ - Property for getting/setting the angle of the rotation. - - :attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. + .. deprecated:: 1.1.0 + Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior` + class instead. """ - rotate_value_axis = ListProperty((0, 0, 1)) - """ - Property for getting/setting the axis of the rotation. - - :attr:`rotate_value_axis` is an :class:`~kivy.properties.NumericProperty` - and defaults to `(0, 0, 1)`. - """ + def __init__(self, **kwargs): + super().__init__(**kwargs) + Logger.warning( + "KivyMD: " + "The `RotateWidget` class has been deprecated. " + "Use the `RotateBehavior` class instead." + ) diff --git a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv deleted file mode 100644 index 5fe002f..0000000 --- a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv +++ /dev/null @@ -1,10 +0,0 @@ - - canvas.before: - PushMatrix - Scale: - x: self.scale_value_x - y: self.scale_value_y - z: self.scale_value_x - origin: self.center - canvas.after: - PopMatrix diff --git a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py index 9f9204f..3fe6985 100644 --- a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py +++ b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py @@ -2,149 +2,33 @@ Templates/ScaleWidget ===================== -.. versionadded:: 1.0.0 +.. deprecated:: 1.1.0 Base class for controlling the scale of the widget. -.. note:: See `kivy.graphics.Scale - `_ - for more information. - -Kivy ----- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.properties import NumericProperty - from kivy.uix.button import Button - from kivy.app import App - - - KV = ''' - Screen: - - ScaleButton: - size_hint: .5, .5 - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.change_scale(self) - - canvas.before: - PushMatrix - Scale: - x: self.scale_value_x - y: self.scale_value_y - z: self.scale_value_x - origin: self.center - canvas.after: - PopMatrix - ''' - - - class ScaleButton(Button): - scale_value_x = NumericProperty(1) - scale_value_y = NumericProperty(1) - scale_value_z = NumericProperty(1) - - - class Test(App): - def build(self): - return Builder.load_string(KV) - - def change_scale(self, instance_button: Button) -> None: - Animation( - scale_value_x=0.5, - scale_value_y=0.5, - scale_value_z=0.5, - d=0.3, - ).start(instance_button) - - - Test().run() - -KivyMD ------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton - from kivymd.uix.templates import ScaleWidget - - KV = ''' - MDScreen: - - ScaleButton: - size_hint: .5, .5 - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.change_scale(self) - elevation:0 - ''' - - - class ScaleButton(MDRaisedButton, ScaleWidget): - pass - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def change_scale(self, instance_button: MDRaisedButton) -> None: - Animation( - scale_value_x=0.5, - scale_value_y=0.5, - scale_value_z=0.5, - d=0.3, - ).start(instance_button) - - - Test().run() +.. note:: `ScaleWidget` class has been deprecated. Please use + `ScaleBehavior `_ + class instead. """ __all__ = ("ScaleWidget",) -import os +from kivy import Logger -from kivy.lang import Builder -from kivy.properties import NumericProperty - -from kivymd import uix_path - -with open( - os.path.join(uix_path, "templates", "scalewidget", "scalewidget.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) +from kivymd.uix.behaviors import ScaleBehavior -class ScaleWidget: - """Base class for controlling the scale of the widget.""" - - scale_value_x = NumericProperty(1) +class ScaleWidget(ScaleBehavior): """ - X-axis value. - - :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. + .. deprecated:: 1.1.0 + Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` + class instead. """ - scale_value_y = NumericProperty(1) - """ - Y-axis value. - - :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - scale_value_z = NumericProperty(1) - """ - Z-axis value. - - :attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ + def __init__(self, **kwargs): + super().__init__(**kwargs) + Logger.warning( + "KivyMD: " + "The `ScaleWidget` class has been deprecated. " + "Use the `ScaleBehavior` class instead." + ) diff --git a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv deleted file mode 100644 index 04807dc..0000000 --- a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv +++ /dev/null @@ -1,19 +0,0 @@ - - canvas.before: - StencilPush - RoundedRectangle: - pos: root.pos - size: root.size - # FIXME: Sometimes the radius has the value [], which get a - # `GraphicException: Invalid radius value, must be list of tuples/numerics` error - radius: root.radius if root.radius else [0, 0, 0, 0] - StencilUse - canvas.after: - StencilUnUse - RoundedRectangle: - pos: root.pos - size: root.size - # FIXME: Sometimes the radius has the value [], which get a - # `GraphicException: Invalid radius value, must be list of tuples/numerics` error - radius: root.radius if root.radius else [0, 0, 0, 0] - StencilPop diff --git a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py index 3aa8716..a231588 100644 --- a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py +++ b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py @@ -2,115 +2,33 @@ Templates/StencilWidget ======================= -.. versionadded:: 1.0.0 +.. deprecated:: 1.1.0 Base class for controlling the stencil instructions of the widget. -.. note:: See `Stencil instructions - `_ - for more information. - -Kivy ----- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.app import App - - KV = ''' - Carousel: - - Button: - size_hint: .9, .8 - pos_hint: {"center_x": .5, "center_y": .5} - - canvas.before: - StencilPush - RoundedRectangle: - pos: root.pos - size: root.size - StencilUse - canvas.after: - StencilUnUse - RoundedRectangle: - pos: root.pos - size: root.size - StencilPop - ''' - - - class Test(App): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -KivyMD ------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.templates import StencilWidget - from kivymd.utils.fitimage import FitImage - - KV = ''' - MDCarousel: - - StencilImage: - size_hint: .9, .8 - pos_hint: {"center_x": .5, "center_y": .5} - source: "image.png" - ''' - - - class StencilImage(FitImage, StencilWidget): - pass - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() +.. note:: `StencilWidget` class has been deprecated. Please use + `StencilBehavior `_ + class instead. """ __all__ = ("StencilWidget",) -import os +from kivy import Logger -from kivy.lang import Builder -from kivy.properties import VariableListProperty - -from kivymd import uix_path - -with open( - os.path.join(uix_path, "templates", "stencilwidget", "stencilwidget.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) +from kivymd.uix.behaviors import StencilBehavior -class StencilWidget: - """Base class for controlling the stencil instructions of the widget""" - - radius = VariableListProperty([0], length=4) +class StencilWidget(StencilBehavior): """ - Canvas radius. - - .. versionadded:: 1.0.0 - - .. code-block:: python - - # Top left corner slice. - MDWidget: - radius: [25, 0, 0, 0] - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. + .. deprecated:: 1.1.0 + Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior` + class instead. """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + Logger.warning( + "KivyMD: " + "The `StencilWidget` class has been deprecated. " + "Use the `StencilBehavior` class instead." + ) diff --git a/sbapp/kivymd/uix/textfield/__init__.py b/sbapp/kivymd/uix/textfield/__init__.py index 6ac1738..d3786dd 100644 --- a/sbapp/kivymd/uix/textfield/__init__.py +++ b/sbapp/kivymd/uix/textfield/__init__.py @@ -1,2 +1,2 @@ # NOQA F401 -from .textfield import MDTextField, MDTextFieldRect, MDTextFieldRound +from .textfield import MDTextField, MDTextFieldRect diff --git a/sbapp/kivymd/uix/textfield/textfield.kv b/sbapp/kivymd/uix/textfield/textfield.kv index 3f73c32..6f11abb 100644 --- a/sbapp/kivymd/uix/textfield/textfield.kv +++ b/sbapp/kivymd/uix/textfield/textfield.kv @@ -1,7 +1,7 @@ -#:import images_path kivymd.images_path - - + input_filter: self.field_filter + do_backspace: self.do_backspace + canvas.before: Clear @@ -11,16 +11,16 @@ Ellipse: angle_start: 180 angle_end: 360 - pos: self.pos[0] - self.size[1] / 2, self.pos[1] - size: self.size[1], self.size[1] + pos: self.x - self.height / 2 + dp(18), self.y + size: self.height, self.height Ellipse: angle_start: 360 angle_end: 540 - pos: self.size[0] + self.pos[0] - self.size[1]/2.0, self.pos[1] - size: self.size[1], self.size[1] + pos: (self.width - dp(18)) + self.x - self.height / 2.0, self.y + size: self.height, self.height Rectangle: - pos: self.pos - size: self.size + pos: self.x + dp(9), self.y + size: self.width - dp(18), self.height Color: rgba: @@ -33,30 +33,30 @@ (0, 0, 0, 0) Line: points: - self.pos[0], \ - self.pos[1], \ - self.pos[0] + self.size[0], \ - self.pos[1] + self.x + dp(18), \ + self.y, \ + self.x + self.width - dp(18), \ + self.y Line: points: - self.pos[0], \ - self.pos[1] + self.size[1], \ - self.pos[0] + self.size[0], \ - self.pos[1] + self.size[1] + self.x + dp(18), \ + self.y + self.height, \ + self.x + self.width - dp(18), \ + self.y + self.height Line: ellipse: - self.pos[0] - self.size[1] / 2, \ - self.pos[1], \ - self.size[1], \ - self.size[1], \ + self.x - self.height / 2 + dp(18), \ + self.y, \ + self.height, \ + self.height, \ 180, \ 360 Line: ellipse: - self.size[0] + self.pos[0] - self.size[1] / 2.0, \ - self.pos[1], \ - self.size[1], \ - self.size[1], \ + self.width + self.x - self.height / 2.0 - dp(18), \ + self.y, \ + self.height, \ + self.height, \ 360, \ 540 @@ -100,7 +100,8 @@ texture: self._helper_text_label.texture size: self._helper_text_label.texture_size pos: - self.x + (dp(16) if self.mode == "fill" else 0), \ + self.x + (dp(16) if self.mode == "fill" else \ + (0 if self.mode not in ("round", "rectangle") else dp(12))), \ self.y + (dp(-18) if self.mode in ("fill", "rectangle", "round") else dp(-2)) # Right/left icon texture. @@ -114,15 +115,22 @@ (self._icon_right_label.texture_size if self.icon_right else self._icon_left_label.texture_size) pos: ( \ - (self.width + self.x) - (self._icon_right_label.texture_size[1]) - dp(8), \ - self.center[1] - self._icon_right_label.texture_size[1] / 2 + ((dp(8) if self.mode != "round" else 0) if self.mode != "fill" else 0) \ + (self.width + self.x - (0 if self.mode != "round" else dp(4))) - \ + (self._icon_right_label.texture_size[1]) - dp(8), \ + self.center[1] - self._icon_right_label.texture_size[1] / 2 + ((dp(8) \ + if self.mode != "round" else 0) if self.mode != "fill" else 0) \ if self.mode != "rectangle" else \ self.center[1] - self._icon_right_label.texture_size[1] / 2 - dp(4) \ ) \ if self.icon_right else \ ( \ - self.x + (dp(0) if self.mode != "fill" else (dp(4) if not self.icon_left else dp(16))), \ - self.center[1] - self._icon_left_label.texture_size[1] / 2 + (((dp(4) if self.mode != "round" else 0) if self.mode not in ("rectangle", "fill") else dp(8)) if self.mode != "fill" else 0) \ + self.x + ((dp((0 if self.mode != "round" else 12)) \ + if self.mode != "rectangle" else dp(12)) \ + if self.mode != "fill" else (dp(4) if not self.icon_left else dp(16))), \ + + self.center[1] - self._icon_left_label.texture_size[1] / 2 + (((dp(4) \ + if self.mode != "round" else 0) if self.mode not in ("rectangle", "fill") \ + else dp(8)) if self.mode != "fill" else 0) \ if self.mode != "rectangle" else \ self.center[1] - self._icon_left_label.texture_size[1] / 2 - dp(4) \ ) @@ -134,7 +142,7 @@ texture: self._max_length_label.texture size: self._max_length_label.texture_size pos: - self.x + self.width - self._max_length_label.texture_size[0] - (dp(12) if self.mode != "round" else dp(0)), \ + self.x + self.width - self._max_length_label.texture_size[0] - dp(12), \ self.y - (dp(2) if self.mode == "line" else dp(18)) # Cursor blink. @@ -157,11 +165,16 @@ pos: ( \ self.x + ((dp(16) if not self.icon_left else dp(52)) \ - if self.mode == "fill" else (0 if not self.icon_left else \ - dp(36))) if not self.focus and not self.text else \ - self.x + ((dp(16) if self.mode != "round" else dp(36)) if self.mode in ("fill", "rectangle", "round") and \ - self.icon_left else 0) + self._hint_x + if self.mode == "fill" else ( \ + ((0 if self.mode != "round" else dp(12)) if self.mode != "rectangle" else dp(12)) \ + if not self.icon_left else \ + (dp(36 if self.mode != "round" else 42) if self.mode != "rectangle" else dp(42)))) \ + if not self.focus and not self.text else \ + self.x + ((dp(16) if self.mode != "round" else dp(36 if not self.icon_left else 42)) \ + if self.mode in ("fill", "rectangle", "round") and \ + self.icon_left else (0 if self.mode != "round" else dp(12))) + self._hint_x ), \ + self.y + self.height + (((dp(4) if self.mode != "round" else dp(10)) \ if self.mode != "line" else \ dp(-6)) if self.mode != "rectangle" else dp(-4)) - self._hint_y @@ -176,12 +189,14 @@ width: dp(1) if self.mode == "rectangle" else dp(0.00001) points: ( - self.x + self._line_blank_space_right_point, self.top - self._hint_text_label.texture_size[1] // 2, - self.right + dp(12), self.top - self._hint_text_label.texture_size[1] // 2, - self.right + dp(12), self.y, - self.x - dp(12), self.y, - self.x - dp(12), self.top - self._hint_text_label.texture_size[1] // 2, - self.x + self._line_blank_space_left_point, self.top - self._hint_text_label.texture_size[1] // 2 + self.x + self._line_blank_space_right_point, + self.top - self._hint_text_label.texture_size[1] // 2, + self.right, self.top - self._hint_text_label.texture_size[1] // 2, + self.right, self.y, + self.x, self.y, + self.x, self.top - self._hint_text_label.texture_size[1] // 2, + self.x + self._line_blank_space_left_point, + self.top - self._hint_text_label.texture_size[1] // 2 ) # Text color. @@ -197,10 +212,32 @@ foreground_color: self.theme_cls.text_color bold: False padding: - (0 if not self.icon_left else "36dp") if self.mode != "fill" else ("16dp" if not self.icon_left else "52dp"), \ + ( \ + ((0 if self.mode != "round" else "12dp") \ + if self.mode != "rectangle" else "12dp") \ + if not self.icon_left else \ + (("36dp" if self.mode != "round" else "42dp") \ + if self.mode != "rectangle" else "42dp") \ + ) \ + if self.mode != "fill" else \ + ("16dp" if not self.icon_left else "52dp"), \ + "24dp" if self.mode != "round" else "8dp", \ - 0 if self.mode != "fill" and not self.icon_right else ("14dp" if not self.icon_right else self._icon_right_label.texture_size[1] + dp(20)), \ - "8dp" if self.mode == "fill" else (("22dp" if self.mode != "round" else "8dp") if self.icon_left and self.mode != "rectangle" else ("16dp" if self.mode in ("fill", "rectangle") else "20dp" if self.mode != "round" else "8dp")) + + ((0 if self.mode != "round" and not self.icon_left else dp(12)) \ + if self.mode != "rectangle" else "12dp") \ + if self.mode != "fill" and not self.icon_right else \ + ( \ + "14dp" \ + if not self.icon_right else \ + self._icon_right_label.texture_size[1] + (dp(20) \ + if self.mode != "round" else dp(24))), \ + + "8dp" if self.mode == "fill" else \ + (("22dp" if self.mode != "round" else "8dp") \ + if self.icon_left and self.mode != "rectangle" else \ + ("16dp" if self.mode in ("fill", "rectangle") else \ + "20dp" if self.mode != "round" else "8dp")) multiline: False size_hint_y: None height: self.minimum_height diff --git a/sbapp/kivymd/uix/textfield/textfield.py b/sbapp/kivymd/uix/textfield/textfield.py index 727498b..08ea932 100755 --- a/sbapp/kivymd/uix/textfield/textfield.py +++ b/sbapp/kivymd/uix/textfield/textfield.py @@ -14,7 +14,6 @@ Components/TextField `KivyMD` provides the following field classes for use: - MDTextField_ -- MDTextFieldRound_ - MDTextFieldRect_ .. Note:: :class:`~MDTextField` inherited from @@ -79,15 +78,15 @@ parameter to `True`: from kivymd.app import MDApp KV = ''' - BoxLayout: - padding: "10dp" + MDScreen: MDTextField: id: text_field_error hint_text: "Helper text on error (press 'Enter')" helper_text: "There will always be a mistake" helper_text_mode: "on_error" - pos_hint: {"center_y": .5} + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_x: .5 ''' @@ -97,6 +96,8 @@ parameter to `True`: self.screen = Builder.load_string(KV) def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" self.screen.ids.text_field_error.bind( on_text_validate=self.set_error_message, on_focus=self.set_error_message, @@ -119,6 +120,7 @@ Helper text mode `'on_error'` (with required) MDTextField: hint_text: "required = True" + text: "required = True" required: True helper_text_mode: "on_error" helper_text: "Enter text" @@ -186,7 +188,7 @@ Round mode max_text_length: 15 helper_text: "Massage" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.gif :align: center .. MDTextFieldRect: @@ -203,6 +205,7 @@ MDTextFieldRect MDTextFieldRect: size_hint: 1, None height: "30dp" + background_color: app.theme_cls.bg_normal .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif :align: center @@ -274,22 +277,21 @@ Clickable icon for MDTextField See more information in the :class:`~MDTextFieldRect` class. """ -__all__ = ("MDTextField", "MDTextFieldRect", "MDTextFieldRound") +__all__ = ("MDTextField", "MDTextFieldRect") import os import re +from datetime import date from typing import Union from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder -from kivy.logger import Logger from kivy.metrics import dp, sp from kivy.properties import ( AliasProperty, BooleanProperty, ColorProperty, - DictProperty, ListProperty, NumericProperty, ObjectProperty, @@ -302,6 +304,7 @@ from kivy.uix.textinput import TextInput from kivymd import uix_path from kivymd.font_definitions import theme_font_styles from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import DeclarativeBehavior from kivymd.uix.label import MDIcon with open( @@ -310,6 +313,220 @@ with open( Builder.load_string(kv_file.read()) +# TODO: Add a class to work with the phone number mask. + + +class AutoFormatTelephoneNumber: + """ + Implements automatic formatting of the text entered in the text field + according to the mask, for example '+38 (###) ### ## ##'. + """ + + def __init__(self): + self._backspace = False + + def isnumeric(self, value): + try: + int(value) + return True + except ValueError: + return False + + def do_backspace(self, *args): + if self.validator and self.validator == "phone": + self._backspace = True + text = self.text + text = text[:-1] + self.text = text + self._backspace = False + + def field_filter(self, value, boolean): + if self.validator and self.validator == "phone": + if len(self.text) == 14: + return + if self.isnumeric(value): + return value + return value + + def format(self, value): + if value != "" and not value.isspace() and not self._backspace: + if len(value) <= 1 and self.focus: + self.text = value + self._check_cursor() + elif len(value) == 4: + start = self.text[:-1] + end = self.text[-1] + self.text = "%s) %s" % (start, end) + self._check_cursor() + elif len(value) == 8: + self.text += "-" + self._check_cursor() + elif len(value) in [12, 16]: + start = self.text[:-1] + end = self.text[-1] + self.text = "%s-%s" % (start, end) + self._check_cursor() + + def _check_cursor(self): + def set_pos_cursor(pos_corsor, interval=0.5): + self.cursor = (pos_corsor, 0) + + if self.focus: + Clock.schedule_once(lambda x: set_pos_cursor(len(self.text)), 0.1) + + +class Validator: + """Container class for various validation methods.""" + + datetime_date = ObjectProperty() + """ + The last valid date as a object. + + :attr:`datetime_date` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + date_interval = ListProperty([None, None]) + """ + The date interval that is valid for input. + Can be entered as objects or a string format. + Both values or just one value can be entered. + + In string format, must follow the current date_format. + Example: Given date_format -> "mm/dd/yyyy" + Input examples -> "12/31/1900", "12/31/2100" or "12/31/1900", None. + + :attr:`date_interval` is an :class:`~kivy.properties.ListProperty` + and defaults to `[None, None]`. + """ + + date_format = OptionProperty( + None, + options=[ + "dd/mm/yyyy", + "mm/dd/yyyy", + "yyyy/mm/dd", + ], + ) + + """ + Format of date strings that will be entered. + Available options are: `'dd/mm/yyyy'`, `'mm/dd/yyyy'`, `'yyyy/mm/dd'`. + + :attr:`date_format` is an :class:`~kivy.properties.OptionProperty` + and defaults to `None`. + """ + + def is_email_valid(self, text: str) -> bool: + if not re.match(r"[^@]+@[^@]+\.[^@]+", text): + return True + return False + + def is_time_valid(self, text: str) -> bool: + if re.match(r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$", text) or re.match( + r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$", text + ): + return False + + return True + + def is_date_valid(self, text: str) -> bool: + if not self.date_format: + raise Exception("TextInput date_format was not defined.") + + # Regex strings. + dd = "[0][1-9]|[1-2][0-9]|[3][0-1]" + mm = "[0][1-9]|[1][0-2]" + yyyy = "[0-9][0-9][0-9][0-9]" + fmt = self.date_format.split("/") + largs = locals() + # Access the local variables dict in the correct format based on + # date_format split. Example: "mm/dd/yyyy" -> ["mm", "dd", "yyyy"] + # largs[fmt[0]] would be largs["mm"] so the month regex string. + if re.match( + f"^({largs[fmt[0]]})/({largs[fmt[1]]})/({largs[fmt[2]]})$", text + ): + input_split = text.split("/") + largs[fmt[0]] = input_split[0] + largs[fmt[1]] = input_split[1] + largs[fmt[2]] = input_split[2] + # Organize input into correct slots and try to convert + # to datetime object. This way February exceptions are + # tested. Also tests with the date_interval are simpler + # using datetime objects. + try: + datetime = date( + int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + ) + except ValueError: + return True + + if self.date_interval: + if ( + self.date_interval[0] + and not self.date_interval[0] <= datetime + or self.date_interval[1] + and not datetime <= self.date_interval[1] + ): + return True + + self.datetime_date = datetime + return False + return True + + def on_date_interval(self, *args) -> None: + """Default event handler for date_interval input.""" + + def on_date_interval(): + if not self.date_format: + raise Exception("TextInput date_format was not defined.") + + fmt = self.date_format.split("/") + largs = {} + # Convert string inputs into datetime.date objects and store + # them back into self.date_interval. + try: + if self.date_interval[0] and not isinstance( + self.date_interval[0], date + ): + split = self.date_interval[0].split("/") + largs[fmt[0]] = split[0] + largs[fmt[1]] = split[1] + largs[fmt[2]] = split[2] + self.date_interval[0] = date( + int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + ) + if self.date_interval[1] and not isinstance( + self.date_interval[1], date + ): + split = self.date_interval[1].split("/") + largs[fmt[0]] = split[0] + largs[fmt[1]] = split[1] + largs[fmt[2]] = split[2] + self.date_interval[1] = date( + int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + ) + + except Exception: + raise Exception( + r"TextInput date_interval was defined incorrectly, it must " + r"be composed of objects or strings" + r" following current date_format." + ) + + # Test if the interval is valid. + if isinstance(self.date_interval[0], date) and isinstance( + self.date_interval[1], date + ): + if self.date_interval[0] >= self.date_interval[1]: + raise Exception( + "TextInput date_interval last date must be greater" + " than the first date or set to None." + ) + + Clock.schedule_once(lambda x: on_date_interval()) + + class MDTextFieldRect(ThemableBehavior, TextInput): line_anim = BooleanProperty(True) """ @@ -382,7 +599,13 @@ class TextfieldLabel(ThemableBehavior, Label): self.font_size = sp(self.theme_cls.font_styles[self.font_style][1]) -class MDTextField(ThemableBehavior, TextInput): +class MDTextField( + DeclarativeBehavior, + ThemableBehavior, + TextInput, + Validator, + AutoFormatTelephoneNumber, +): helper_text = StringProperty() """ Text for ``helper_text`` mode. @@ -418,20 +641,6 @@ class MDTextField(ThemableBehavior, TextInput): and defaults to `False`. """ - color_mode = OptionProperty( - "primary", options=["primary", "accent", "custom"], deprecated=True - ) - """ - Color text mode. Available options are: `'primary'`, `'accent'`, - `'custom'`. - - .. deprecated:: 1.0.0 - Don't use this attribute. - - :attr:`color_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'primary'`. - """ - mode = OptionProperty( "line", options=["rectangle", "round", "fill", "line"] ) @@ -443,17 +652,185 @@ class MDTextField(ThemableBehavior, TextInput): and defaults to `'line'`. """ + phone_mask = StringProperty("") + + validator = OptionProperty(None, options=["date", "email", "time", "phone"]) + """ + The type of text field for entering Email, time, etc. + Automatically sets the type of the text field as "error" if the user input + does not match any of the set validation types. + Available options are: `'date'`, `'email'`, `'time'`. + + When using `'date'`, :attr:`date_format` must be defined. + + .. versionadded:: 1.1.0 + + .. code-block:: python + + MDTextField: + hint_text: "Email" + helper_text: "user@gmail.com" + validator: "email" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator.png + :align: center + + .. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + + MDBoxLayout: + orientation: "vertical" + spacing: "20dp" + adaptive_height: True + size_hint_x: .8 + pos_hint: {"center_x": .5, "center_y": .5} + + MDTextField: + hint_text: "Date dd/mm/yyyy without limits" + helper_text: "Enter a valid dd/mm/yyyy date" + validator: "date" + date_format: "dd/mm/yyyy" + + MDTextField: + hint_text: "Date mm/dd/yyyy without limits" + helper_text: "Enter a valid mm/dd/yyyy date" + validator: "date" + date_format: "mm/dd/yyyy" + + MDTextField: + hint_text: "Date yyyy/mm/dd without limits" + helper_text: "Enter a valid yyyy/mm/dd date" + validator: "date" + date_format: "yyyy/mm/dd" + + MDTextField: + hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval" + helper_text: "Enter a valid dd/mm/yyyy date" + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: "01/01/1900", "01/01/2100" + + MDTextField: + hint_text: "Date dd/mm/yyyy in [01/01/1900, None] interval" + helper_text: "Enter a valid dd/mm/yyyy date" + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: "01/01/1900", None + + MDTextField: + hint_text: "Date dd/mm/yyyy in [None, 01/01/2100] interval" + helper_text: "Enter a valid dd/mm/yyyy date" + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: None, "01/01/2100" + ''' + + + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return Builder.load_string(KV) + + + Test().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.screen import MDScreen + from kivymd.uix.textfield import MDTextField + + + class Test(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" + return ( + MDScreen( + MDBoxLayout( + MDTextField( + hint_text="Date dd/mm/yyyy without limits", + helper_text="Enter a valid dd/mm/yyyy date", + validator="date", + date_format="dd/mm/yyyy", + ), + MDTextField( + hint_text="Date mm/dd/yyyy without limits", + helper_text="Enter a valid mm/dd/yyyy date", + validator="date", + date_format="mm/dd/yyyy", + ), + MDTextField( + hint_text="Date yyyy/mm/dd without limits", + helper_text="Enter a valid yyyy/mm/dd date", + validator="date", + date_format="yyyy/mm/dd", + ), + MDTextField( + hint_text="Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval", + helper_text="Enter a valid dd/mm/yyyy date", + validator="date", + date_format="dd/mm/yyyy", + date_interval=["01/01/1900", "01/01/2100"], + ), + MDTextField( + hint_text="Date dd/mm/yyyy in [01/01/1900, None] interval", + helper_text="Enter a valid dd/mm/yyyy date", + validator="date", + date_format="dd/mm/yyyy", + date_interval=["01/01/1900", None], + ), + MDTextField( + hint_text="Date dd/mm/yyyy in [None, 01/01/2100] interval", + helper_text="Enter a valid dd/mm/yyyy date", + validator="date", + date_format="dd/mm/yyyy", + date_interval=[None, "01/01/2100"], + ), + orientation="vertical", + spacing="20dp", + adaptive_height=True, + size_hint_x=0.8, + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + ) + + + Test().run() + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator-date.png + :align: center + + :attr:`validator` is an :class:`~kivy.properties.OptionProperty` + and defaults to `None`. + """ + line_color_normal = ColorProperty([0, 0, 0, 0]) """ - Line color normal (static underline line) in ``rgba`` format. + Line color normal (static underline line) in (r, g, b, a) or string format. .. code-block:: kv MDTextField: hint_text: "line_color_normal" - line_color_normal: 1, 0, 1, 1 + line_color_normal: "red" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.png :align: center :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty` @@ -462,13 +839,13 @@ class MDTextField(ThemableBehavior, TextInput): line_color_focus = ColorProperty([0, 0, 0, 0]) """ - Line color focus (active underline line) in ``rgba`` format. + Line color focus (active underline line) in (r, g, b, a) or string format. .. code-block:: kv MDTextField: hint_text: "line_color_focus" - line_color_focus: 0, 1, 0, 1 + line_color_focus: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif :align: center @@ -487,27 +864,26 @@ class MDTextField(ThemableBehavior, TextInput): error_color = ColorProperty([0, 0, 0, 0]) """ - Error color in ``rgba`` format for ``required = True``. + Error color in (r, g, b, a) or string format for ``required = True``. :attr:`error_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. """ - fill_color = ColorProperty([0, 0, 0, 0], deprecated=True) - """ - The background color of the fill in rgba format when the ``mode`` parameter - is "fill". - - .. deprecated:: 1.0.0 - Use :attr:`fill_color_normal` and :attr:`fill_color_focus` instead. - - :attr:`fill_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - fill_color_normal = ColorProperty([0, 0, 0, 0]) """ - Fill background color in 'fill' mode when text field is out of focus. + Fill background color in (r, g, b, a) or string format in 'fill' mode when] + text field is out of focus. + + .. code=block:: kv + + MDTextField: + hint_text: "Fill mode" + mode: "fill" + fill_color_normal: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-normal.png + :align: center :attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. @@ -515,7 +891,18 @@ class MDTextField(ThemableBehavior, TextInput): fill_color_focus = ColorProperty([0, 0, 0, 0]) """ - Fill background color in 'fill' mode when the text field has focus. + Fill background color in (r, g, b, a) or string format in 'fill' mode when + the text field has focus. + + .. code=block:: kv + + MDTextField: + hint_text: "Fill mode" + mode: "fill" + fill_color_focus: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-focus.gif + :align: center :attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. @@ -537,20 +924,10 @@ class MDTextField(ThemableBehavior, TextInput): and defaults to `False`. """ - current_hint_text_color = ColorProperty([0, 0, 0, 0], deprecated=True) - """ - Hint text color. - - .. deprecated:: 1.0.0 - Use :attr:`hint_text_color_normal` and :attr:`hint_text_color_focus` instead. - - :attr:`current_hint_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - hint_text_color_normal = ColorProperty([0, 0, 0, 0]) """ - Hint text color when text field is out of focus. + Hint text color in (r, g, b, a) or string format when text field is out + of focus. .. versionadded:: 1.0.0 @@ -558,9 +935,9 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: hint_text: "hint_text_color_normal" - hint_text_color_normal: 0, 1, 0, 1 + hint_text_color_normal: "red" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.png :align: center :attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty` @@ -569,7 +946,8 @@ class MDTextField(ThemableBehavior, TextInput): hint_text_color_focus = ColorProperty([0, 0, 0, 0]) """ - Hint text color when the text field has focus. + Hint text color in (r, g, b, a) or string format when the text field has + focus. .. versionadded:: 1.0.0 @@ -577,7 +955,7 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: hint_text: "hint_text_color_focus" - hint_text_color_focus: 0, 1, 0, 1 + hint_text_color_focus: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif :align: center @@ -588,7 +966,8 @@ class MDTextField(ThemableBehavior, TextInput): helper_text_color_normal = ColorProperty([0, 0, 0, 0]) """ - Helper text color when text field is out of focus. + Helper text color in (r, g, b, a) or string format when text field is out + of focus. .. versionadded:: 1.0.0 @@ -597,7 +976,7 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: helper_text: "helper_text_color_normal" helper_text_mode: "persistent" - helper_text_color_normal: 0, 1, 0, 1 + helper_text_color_normal: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png :align: center @@ -608,7 +987,8 @@ class MDTextField(ThemableBehavior, TextInput): helper_text_color_focus = ColorProperty([0, 0, 0, 0]) """ - Helper text color when the text field has focus. + Helper text color in (r, g, b, a) or string format when the text field has + focus. .. versionadded:: 1.0.0 @@ -617,7 +997,7 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: helper_text: "helper_text_color_focus" helper_text_mode: "persistent" - helper_text_color_focus: 0, 1, 0, 1 + helper_text_color_focus: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif :align: center @@ -628,7 +1008,8 @@ class MDTextField(ThemableBehavior, TextInput): icon_right_color_normal = ColorProperty([0, 0, 0, 0]) """ - Color of right icon when text field is out of focus. + Color in (r, g, b, a) or string format of right icon when text field is out + of focus. .. versionadded:: 1.0.0 @@ -637,9 +1018,9 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: icon_right: "language-python" hint_text: "icon_right_color_normal" - icon_right_color_normal: 0, 1, 0, 1 + icon_right_color_normal: "red" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.png :align: center :attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty` @@ -648,7 +1029,8 @@ class MDTextField(ThemableBehavior, TextInput): icon_right_color_focus = ColorProperty([0, 0, 0, 0]) """ - Color of right icon when the text field has focus. + Color in (r, g, b, a) or string format of right icon when the text field + has focus. .. versionadded:: 1.0.0 @@ -657,7 +1039,7 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: icon_right: "language-python" hint_text: "icon_right_color_focus" - icon_right_color_focus: 0, 1, 0, 1 + icon_right_color_focus: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif :align: center @@ -668,47 +1050,30 @@ class MDTextField(ThemableBehavior, TextInput): icon_left_color_normal = ColorProperty([0, 0, 0, 0]) """ - Color of right icon when text field is out of focus. + Color in (r, g, b, a) or string format of right icon when text field is out + of focus. .. versionadded:: 1.0.0 - .. code-block:: kv - - MDTextField: - icon_right: "language-python" - hint_text: "icon_right_color_normal" - icon_left_color_normal: 0, 1, 0, 1 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif - :align: center - :attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. """ icon_left_color_focus = ColorProperty([0, 0, 0, 0]) """ - Color of right icon when the text field has focus. + Color in (r, g, b, a) or string format of right icon when the text field + has focus. .. versionadded:: 1.0.0 - .. code-block:: kv - - MDTextField: - icon_right: "language-python" - hint_text: "icon_right_color_focus" - icon_right_color_focus: 0, 1, 0, 1 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif - :align: center - :attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0]`. """ max_length_text_color = ColorProperty([0, 0, 0, 0]) """ - Text color of the maximum length of characters to be input. + Text color in (r, g, b, a) or string format of the maximum length of + characters to be input. .. versionadded:: 1.0.0 @@ -716,10 +1081,10 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: hint_text: "max_length_text_color" - max_length_text_color: 0, 1, 0, 1 + max_length_text_color: "red" max_text_length: 5 - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.png :align: center :attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty` @@ -752,31 +1117,9 @@ class MDTextField(ThemableBehavior, TextInput): and defaults to `''`. """ - icon_right_color = ColorProperty([0, 0, 0, 1], deprecated=True) - """ - Color of right icon in ``rgba`` format. - - .. deprecated:: 1.0.0 - Don't use this attribute. - - :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 1]`. - """ - - text_color = ColorProperty([0, 0, 0, 0], deprecated=True) - """ - Text color in ``rgba`` format. - - .. deprecated:: 1.0.0 - Use :attr:`text_color_normal` and :attr:`text_color_focus` instead. - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - text_color_normal = ColorProperty([0, 0, 0, 0]) """ - Text color in ``rgba`` format when text field is out of focus. + Text color in (r, g, b, a) or string format when text field is out of focus. .. versionadded:: 1.0.0 @@ -784,9 +1127,9 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: hint_text: "text_color_normal" - text_color_normal: 0, 1, 0, 1 + text_color_normal: "red" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.png :align: center :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` @@ -795,7 +1138,7 @@ class MDTextField(ThemableBehavior, TextInput): text_color_focus = ColorProperty([0, 0, 0, 0]) """ - Text color in ``rgba`` format when text field has focus. + Text color in (r, g, b, a) or string format when text field has focus. .. versionadded:: 1.0.0 @@ -803,7 +1146,7 @@ class MDTextField(ThemableBehavior, TextInput): MDTextField: hint_text: "text_color_focus" - text_color_focus: 0, 1, 0, 1 + text_color_focus: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif :align: center @@ -924,12 +1267,12 @@ class MDTextField(ThemableBehavior, TextInput): # application color palette. _colors_to_updated = ListProperty() - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): self.set_objects_labels() Clock.schedule_once(self._set_attr_names_to_updated) Clock.schedule_once(self.set_colors_to_updated) Clock.schedule_once(self.set_default_colors) - super().__init__(**kwargs) + super().__init__(*args, **kwargs) self.bind( _hint_text_font_size=self._hint_text_label.setter("font_size"), _icon_right_color=self._icon_right_label.setter("text_color"), @@ -937,8 +1280,8 @@ class MDTextField(ThemableBehavior, TextInput): text=self.set_text, ) self.theme_cls.bind( - primary_color=lambda x, y: self.set_default_colors(0, True), - theme_style=lambda x, y: self.set_default_colors(0, True), + primary_color=self.set_default_colors, + theme_style=self.set_default_colors, ) Clock.schedule_once(self.check_text) @@ -988,9 +1331,17 @@ class MDTextField(ThemableBehavior, TextInput): ) if self.error_color == [0, 0, 0, 0] or updated: - self.error_color = self.theme_cls.error_color + self.error_color = ( + self.theme_cls.error_color + if self.error_color == [0, 0, 0, 0] + else self.error_color + ) if self.max_length_text_color == [0, 0, 0, 0] or updated: - self.max_length_text_color = self.theme_cls.disabled_hint_text_color + self.max_length_text_color = ( + self.theme_cls.disabled_hint_text_color + if self.max_length_text_color == [0, 0, 0, 0] + else self.max_length_text_color + ) self._hint_text_color = self.hint_text_color_normal self._text_color_normal = self.text_color_normal @@ -1015,12 +1366,12 @@ class MDTextField(ThemableBehavior, TextInput): def on_progress(*args): self._line_blank_space_right_point = ( - self._hint_text_label.width + dp(5) if not joining else 0 + self._hint_text_label.width + dp(17) if not joining else 0 ) if self.hint_text: animation = Animation( - _line_blank_space_left_point=self._hint_text_label.x - dp(5) + _line_blank_space_left_point=self._hint_text_label.x - dp(-7) if not joining else 0, duration=0.2, @@ -1103,7 +1454,7 @@ class MDTextField(ThemableBehavior, TextInput): t="out_quad", ).start(self) - def set_pos_hint_text(self, y: float, x: float = 0) -> None: + def set_pos_hint_text(self, y: float, x: float = 12) -> None: """Animates the x-axis width and y-axis height of the hint text.""" if self.mode != "round": @@ -1113,7 +1464,7 @@ class MDTextField(ThemableBehavior, TextInput): _hint_x = x else: if y == dp(10): - _hint_x = dp(-16) + _hint_x = dp(-4) else: _hint_x = dp(20) @@ -1159,8 +1510,11 @@ class MDTextField(ThemableBehavior, TextInput): self.text = re.sub("\n", " ", text) if not self.multiline else text self.set_max_text_length() + if self.validator and self.validator == "phone": + pass + # self.format(self.text) - if self.text and self.max_length_text_color and self._get_has_error(): + if (self.text and self.max_length_text_color) or self._get_has_error(): self.error = True if ( self.text @@ -1359,22 +1713,34 @@ class MDTextField(ThemableBehavior, TextInput): if value_height >= self.max_height and self.max_height: self.height = self.max_height - def on_text_color_normal(self, instance_text_field, color: list): + def on_text_color_normal( + self, instance_text_field, color: Union[list, str] + ): self._text_color_normal = color - def on_hint_text_color_normal(self, instance_text_field, color: list): + def on_hint_text_color_normal( + self, instance_text_field, color: Union[list, str] + ): self._hint_text_color = color - def on_helper_text_color_normal(self, instance_text_field, color: list): + def on_helper_text_color_normal( + self, instance_text_field, color: Union[list, str] + ): self._helper_text_color = color - def on_icon_right_color_normal(self, instance_text_field, color: list): + def on_icon_right_color_normal( + self, instance_text_field, color: Union[list, str] + ): self._icon_right_color = color - def on_line_color_normal(self, instance_text_field, color: list): + def on_line_color_normal( + self, instance_text_field, color: Union[list, str] + ): self._line_color_normal = color - def on_max_length_text_color(self, instance_text_field, color: list): + def on_max_length_text_color( + self, instance_text_field, color: Union[list, str] + ): self._max_length_text_color = color def _set_color(self, attr_name: str, color: str, updated: bool) -> None: @@ -1411,6 +1777,13 @@ class MDTextField(ThemableBehavior, TextInput): the :attr:`~MDTextField.required` parameter is set to `True`. """ + if self.validator and self.validator != "phone": + has_error = { + "date": self.is_date_valid, + "email": self.is_email_valid, + "time": self.is_time_valid, + }[self.validator](self.text) + return has_error if self.max_text_length and len(self.text) > self.max_text_length: has_error = True else: @@ -1424,25 +1797,13 @@ class MDTextField(ThemableBehavior, TextInput): """Method override to avoid duplicate hint text texture.""" -class MDTextFieldRound(MDTextField): - """ - .. deprecated:: 1.0.0 - Use :class:`~MDTextField` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `MDTextFieldRound` class has been deprecated. " - "Use the `MDTextField` class instead with `mode='round'`" - ) - - if __name__ == "__main__": + from kivy.core.window import Window from kivy.lang import Builder from kivy.uix.textinput import TextInput + Window.size = (800, 750) + from kivymd.app import MDApp KV = """ @@ -1458,41 +1819,53 @@ MDScreen: MDTextField: hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" mode: "rectangle" max_text_length: 5 MDTextField: icon_left: "git" hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" mode: "rectangle" MDTextField: icon_left: "git" hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" mode: "fill" MDTextField: hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" mode: "fill" MDTextField: hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" MDTextField: icon_left: "git" hint_text: "Label" - helper_text: "Error massage" + helper_text: "Error message" MDTextField: hint_text: "Round mode" mode: "round" max_text_length: 15 - helper_text: "Massage" + helper_text: "Message" + + MDTextField: + hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval" + helper_text: "Enter a valid dd/mm/yyyy date" + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: "01/01/1900", "01/01/2100" + + MDTextField: + hint_text: "Email" + helper_text: "user@gmail.com" + validator: "email" MDFlatButton: text: "SET TEXT" @@ -1502,6 +1875,8 @@ MDScreen: class Test(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def set_text(self): diff --git a/sbapp/kivymd/uix/toolbar/__init__.py b/sbapp/kivymd/uix/toolbar/__init__.py index 15a8177..d6fa24d 100644 --- a/sbapp/kivymd/uix/toolbar/__init__.py +++ b/sbapp/kivymd/uix/toolbar/__init__.py @@ -1,2 +1,2 @@ # NOQA F401 -from .toolbar import MDBottomAppBar, MDToolbar, MDTopAppBar +from .toolbar import MDBottomAppBar, MDTopAppBar diff --git a/sbapp/kivymd/uix/toolbar/toolbar.kv b/sbapp/kivymd/uix/toolbar/toolbar.kv index 2ea68f7..b5538af 100644 --- a/sbapp/kivymd/uix/toolbar/toolbar.kv +++ b/sbapp/kivymd/uix/toolbar/toolbar.kv @@ -24,8 +24,11 @@ if not self.md_bg_bottom_color else \ self.md_bg_bottom_color \ ) \ - if root.parent.md_bg_color == [0, 0, 0, 0] \ - else root.parent.md_bg_color \ + if root.parent and root.parent.md_bg_color == [0, 0, 0, 0] \ + else \ + ( \ + root.parent.md_bg_color if root.parent else root.md_bg_color \ + ) \ ) Mesh: vertices: root._vertices_left diff --git a/sbapp/kivymd/uix/toolbar/toolbar.py b/sbapp/kivymd/uix/toolbar/toolbar.py index 84c5492..0a48548 100755 --- a/sbapp/kivymd/uix/toolbar/toolbar.py +++ b/sbapp/kivymd/uix/toolbar/toolbar.py @@ -121,8 +121,8 @@ Shadow elevation control .. code-block:: kv MDTopAppBar: - title: "Elevation 10" - elevation: 10 + title: "Elevation 4" + elevation: 4 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png :align: center @@ -327,7 +327,7 @@ Material design 3 style :align: center """ -__all__ = ("MDTopAppBar", "MDBottomAppBar", "MDToolbar") +__all__ = ("MDTopAppBar", "MDBottomAppBar", "ActionTopAppBarButton") import os from math import cos, radians, sin @@ -337,10 +337,8 @@ from kivy.animation import Animation from kivy.clock import Clock from kivy.core.window import Window from kivy.lang import Builder -from kivy.logger import Logger from kivy.metrics import dp from kivy.properties import ( - AliasProperty, BooleanProperty, ColorProperty, ListProperty, @@ -356,14 +354,15 @@ from kivymd import uix_path from kivymd.color_definitions import text_colors from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( - FakeRectangularElevationBehavior, + CommonElevationBehavior, + DeclarativeBehavior, + ScaleBehavior, SpecificBackgroundColorBehavior, ) from kivymd.uix.button import MDFloatingActionButton, MDIconButton from kivymd.uix.controllers import WindowController from kivymd.uix.list import OneLineIconListItem from kivymd.uix.menu import MDDropdownMenu -from kivymd.uix.templates import ScaleWidget from kivymd.uix.tooltip import MDTooltip from kivymd.utils.set_bars_colors import set_bars_colors @@ -373,7 +372,7 @@ with open( Builder.load_string(kv_file.read()) -class ActionBottomAppBarButton(MDFloatingActionButton, ScaleWidget): +class ActionBottomAppBarButton(MDFloatingActionButton, ScaleBehavior): """ Implements a floating action button (FAB) for a toolbar with type 'bottom'. """ @@ -408,11 +407,11 @@ class OverFlowMenuItem(OneLineIconListItem): class NotchedBox( ThemableBehavior, - FakeRectangularElevationBehavior, + CommonElevationBehavior, SpecificBackgroundColorBehavior, BoxLayout, ): - elevation = NumericProperty(6) + elevation = NumericProperty(4) notch_radius = NumericProperty() notch_center_x = NumericProperty("100dp") @@ -555,7 +554,7 @@ class NotchedBox( return points -class MDTopAppBar(NotchedBox, WindowController): +class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController): """ :Events: `on_action_button` @@ -945,18 +944,6 @@ class MDTopAppBar(NotchedBox, WindowController): and defaults to `'small'`. """ - round = NumericProperty("10dp", deprecated=True) - """ - Rounding the corners at the notch for a button. - Only for :class:`~MDBottomAppBar` class. - - .. deprecated:: 1.0.0 - Don't use this attribute. - - :attr:`round` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'10dp'`. - """ - # List of action buttons (ActionTopAppBarButton instance) that have been # .added to the overflow _hidden_items = [] @@ -972,8 +959,10 @@ class MDTopAppBar(NotchedBox, WindowController): self.icon_color = self.theme_cls.primary_color self.bind(specific_text_color=self.update_action_bar_text_colors) - self.theme_cls.bind(material_style=self.update_bar_height) - self.theme_cls.bind(primary_palette=self.update_md_bg_color) + self.theme_cls.bind( + material_style=self.update_bar_height, + primary_palette=self.update_md_bg_color, + ) Clock.schedule_once( lambda x: self.on_left_action_items(0, self.left_action_items) @@ -1114,6 +1103,7 @@ class MDTopAppBar(NotchedBox, WindowController): + self.theme_cls.standard_increment / 2 + self._shift ) + self.shadow_offset = [0, 30] self.on_mode(None, self.mode) def on_type_height(self, instance_toolbar, height_type_value: str) -> None: @@ -1309,7 +1299,8 @@ class MDTopAppBar(NotchedBox, WindowController): if material_style_value == "M2": self.anchor_title = "left" elif material_style_value == "M3" and self.type != "bottom": - self.anchor_title = "center" + if not self.anchor_title: + self.anchor_title = "center" elif material_style_value == "M3" and self.type == "bottom": self.anchor_title = "left" return self.anchor_title @@ -1375,7 +1366,7 @@ class MDTopAppBar(NotchedBox, WindowController): ][self.theme_cls.primary_hue] -class MDBottomAppBar(FloatLayout): +class MDBottomAppBar(DeclarativeBehavior, FloatLayout): md_bg_color = ColorProperty([0, 0, 0, 0]) """ Color toolbar. @@ -1384,27 +1375,11 @@ class MDBottomAppBar(FloatLayout): and defaults to `[0, 0, 0, 0]`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.size_hint_y = None def add_widget(self, widget, index=0, canvas=None): if isinstance(widget, MDTopAppBar): super().add_widget(widget) return super().add_widget(widget.action_button) - - -class MDToolbar(MDTopAppBar): - """ - .. deprecated:: 1.0.0 - - Use :class:`~MDTopAppBar` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `MDToolbar` class has been deprecated. Use the `MDTopAppBar` " - "class instead." - ) diff --git a/sbapp/kivymd/uix/tooltip/tooltip.py b/sbapp/kivymd/uix/tooltip/tooltip.py index 3c88333..855119a 100644 --- a/sbapp/kivymd/uix/tooltip/tooltip.py +++ b/sbapp/kivymd/uix/tooltip/tooltip.py @@ -279,7 +279,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): def on_long_touch(self, touch, *args) -> None: if DEVICE_TYPE != "desktop": - self.on_enter(True) + self.on_enter() def on_enter(self, *args) -> None: """ @@ -289,16 +289,18 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): class. """ - if not args and DEVICE_TYPE == "desktop": - if self.tooltip_text: - self._tooltip = MDTooltipViewClass( - tooltip_bg_color=self.tooltip_bg_color, - tooltip_text_color=self.tooltip_text_color, - tooltip_text=self.tooltip_text, - tooltip_font_style=self.tooltip_font_style, - tooltip_radius=self.tooltip_radius, - ) - Clock.schedule_once(self.display_tooltip, -1) + if self.tooltip_text: + if self._tooltip: + self.remove_tooltip() + + self._tooltip = MDTooltipViewClass( + tooltip_bg_color=self.tooltip_bg_color, + tooltip_text_color=self.tooltip_text_color, + tooltip_text=self.tooltip_text, + tooltip_font_style=self.tooltip_font_style, + tooltip_radius=self.tooltip_radius, + ) + Clock.schedule_once(self.display_tooltip, -1) def on_leave(self) -> None: """ @@ -312,7 +314,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): Clock.schedule_once(self.animation_tooltip_dismiss) def on_show(self) -> None: - """Default dismiss event handler.""" + """Default display event handler.""" def on_dismiss(self) -> None: """ diff --git a/sbapp/kivymd/uix/transition/transition.py b/sbapp/kivymd/uix/transition/transition.py index 49bd657..169584d 100644 --- a/sbapp/kivymd/uix/transition/transition.py +++ b/sbapp/kivymd/uix/transition/transition.py @@ -35,7 +35,9 @@ __all__ = ( "MDTransitionBase", ) +from kivy import Logger from kivy.animation import Animation, AnimationTransition +from kivy.properties import DictProperty from kivy.uix.screenmanager import ( ScreenManagerException, SlideTransition, @@ -43,13 +45,41 @@ from kivy.uix.screenmanager import ( TransitionBase, ) +from kivymd.uix.hero import MDHeroFrom, MDHeroTo from kivymd.uix.screenmanager import MDScreenManager class MDTransitionBase(TransitionBase): + """ + TransitionBase is used to animate 2 screens within the + :class:`~kivymd.uix.screenmanager.MDScreenManager`. + + For more + information, see in the :class:`~kivy.uix.screenmanager.TransitionBase` + class documentation. + """ + _direction = "in" - hero_widget = None - hero_from_widget = None # kivymd.uix.hero.MDHeroFrom object + # Collection of child widgets of all 'MDHeroFrom' widgets that are + # on the screen, for example: + # + # MDScreen: + # + # MDHeroFrom: + # tag: "kivymd" + # + # FitImage: + # + # MDHeroFrom: + # tag: "kivy" + # + # FitImage: + # + # { + # 'kivy': , + # 'kivymd': , + # } + _hero_from_widget_children = DictProperty() def start(self, instance_screen_manager: MDScreenManager) -> None: super().start(instance_screen_manager) @@ -59,67 +89,160 @@ class MDTransitionBase(TransitionBase): ]() def animated_hero_in(self) -> None: - if self.manager._heroes_data and self.manager.current_hero: - self.hero_from_widget = self.manager.get_hero_from_widget() - self._check_widget_properties() - self.hero_widget = self.hero_from_widget.children[0] - self.hero_from_widget.remove_widget(self.hero_widget) + """Animates the flight of heroes from screen **A** to screen **B**.""" - self.hero_widget.pos = self.screen_out.to_widget( - *self.hero_from_widget.to_window(*self.hero_from_widget.pos) - ) - self.hero_widget.size = self.hero_from_widget.size - self.manager.get_root_window().add_widget(self.hero_widget) + if self.manager._heroes_data and self.manager.current_heroes: + for hero_from_widget in self.manager.get_hero_from_widget(): + for heroes_tag in self.manager.current_heroes: + if heroes_tag == hero_from_widget.tag: + self._check_widget_properties(hero_from_widget) - Animation( - size=self.screen_in.hero_to.size, - d=self.duration, - pos=self.screen_in.hero_to.pos, - ).start(self.hero_widget) - self.hero_from_widget.dispatch( - "on_transform_in", self.hero_widget, self.duration - ) + # Get child widget of the 'MDHeroFrom' container. + hero_widget = hero_from_widget.children[0] + self._hero_from_widget_children[ + hero_from_widget.tag + ] = hero_widget + + # Removing the child widget from the 'MDHeroFrom' + # container. + hero_from_widget.remove_widget(hero_widget) + + # We set the size, position of the child widget of the + # 'MDHeroFrom' container and add this widget to the + # root window. + hero_widget.pos = self.screen_out.to_widget( + *hero_from_widget.to_window(*hero_from_widget.pos) + ) + hero_widget.size = hero_from_widget.size + self.manager.get_root_window().add_widget(hero_widget) + + # Animating widgets added to the root window. + if self.screen_in.heroes_to: + for hero_to_widget in self.screen_in.heroes_to: + self._check_hero_to_widget_tag( + hero_to_widget, hero_from_widget + ) + if hero_to_widget.tag == heroes_tag: + Animation( + size=hero_to_widget.size, + d=self.duration, + pos=hero_to_widget.pos, + ).start(hero_widget) + hero_from_widget.dispatch( + "on_transform_in", + hero_widget, + self.duration, + ) def animated_hero_out(self) -> None: - if self.manager._heroes_data and self.manager.current_hero: - self.screen_out.hero_to.remove_widget(self.hero_widget) - self.manager.get_root_window().add_widget(self.hero_widget) + """Animates the flight of heroes from screen **B** to screen **A**.""" - self.hero_from_widget.dispatch( - "on_transform_out", self.hero_widget, self.duration - ) - Animation( - pos=self.screen_in.to_widget( - *self.hero_from_widget.to_window(*self.hero_from_widget.pos) - ), - size=self.hero_from_widget.size, - d=self.duration, - ).start(self.hero_widget) + if ( + self.manager._heroes_data + and self.manager.current_heroes + and self.screen_out.heroes_to + ): + + for heroes_tag in self.manager.current_heroes: + for hero_to_widget in self.screen_out.heroes_to: + if hero_to_widget.tag == heroes_tag: + hero_from_children = self._hero_from_widget_children[ + heroes_tag + ] + hero_to_widget.remove_widget(hero_from_children) + self.manager.get_root_window().add_widget( + hero_from_children + ) + + for ( + hero_from_widget + ) in self.manager.get_hero_from_widget(): + hero_from_widget.dispatch( + "on_transform_out", + self._hero_from_widget_children[ + hero_from_widget.tag + ], + self.duration, + ) + Animation( + pos=self.screen_in.to_widget( + *hero_from_widget.to_window( + *hero_from_widget.pos + ) + ), + size=hero_from_widget.size, + d=self.duration, + ).start( + self._hero_from_widget_children[ + hero_from_widget.tag + ] + ) def on_complete(self) -> None: + """ + Override method. + See :attr:`kivy.uix.screenmanager.TransitionBase.on_complete'. + """ + super().on_complete() + if self.manager._heroes_data and self.manager.current_heroes: + for hero_from_widget in self.manager.get_hero_from_widget(): + for heroes_tag in self.manager.current_heroes: + if heroes_tag == hero_from_widget.tag: + hero_from_children = self._hero_from_widget_children[ + heroes_tag + ] + self.manager.get_root_window().remove_widget( + hero_from_children + ) + + # Adding a child widget from the 'MDHeraFrom' container + # to the 'MDHeroTo' container. + if self._direction == "in": + for hero_to_widget in self.screen_in.heroes_to: + if hero_to_widget.tag == heroes_tag: + hero_to_widget.add_widget( + hero_from_children + ) + # Restores the child widget for the 'MDHeraFrom' + # container. + elif self._direction == "out": + hero_from_widget.add_widget(hero_from_children) + if self._direction == "out": self._direction = "in" - if self.manager._heroes_data and self.manager.current_hero: - self.manager.get_root_window().remove_widget(self.hero_widget) - self.hero_from_widget.add_widget(self.hero_widget) else: self._direction = "out" - if self.manager._heroes_data and self.manager.current_hero: - self.manager.get_root_window().remove_widget(self.hero_widget) - self.screen_in.hero_to.add_widget(self.hero_widget) - def _check_widget_properties(self): - if not self.screen_in.hero_to: + # Checks the attributes for the 'self.screen_in' screen. + # Called from the animated_hero_in method. + def _check_widget_properties(self, hero_from_widget: MDHeroFrom): + if not self.screen_in.heroes_to: raise Exception( - f"The `hero_to` attribute is not specified for screen {self.screen_in}" + f"The `heroes_to` attribute is not specified for screen " + f"{self.screen_in}" ) - if len(self.hero_from_widget.children) > 1: + # The 'MDHeroFrom' widget allows you to place only one widget in + # itself. + if len(hero_from_widget.children) > 1: raise Exception( - f"{self.hero_from_widget.__class__} accept only one widget" + f"{hero_from_widget.__class__} accept only one widget" ) + # For new API support. + def _check_hero_to_widget_tag( + self, hero_to_widget: MDHeroTo, hero_from_widget: MDHeroFrom + ) -> None: + if not hero_to_widget.tag: + Logger.warning( + "KivyMD: " + f"Set the tag '{hero_from_widget.tag}' " + f"for the {hero_to_widget} widget to the same " + f"as for the {hero_from_widget} widget" + ) + hero_to_widget.tag = hero_from_widget.tag + class MDSwapTransition(SwapTransition, MDTransitionBase): pass diff --git a/sbapp/kivymd/uix/widget.py b/sbapp/kivymd/uix/widget.py index 23e4b80..044722b 100644 --- a/sbapp/kivymd/uix/widget.py +++ b/sbapp/kivymd/uix/widget.py @@ -8,7 +8,7 @@ with some widget properties. For example: Widget ------ -.. code-block:: +.. code-block:: kv Widget: size_hint: .5, None @@ -25,7 +25,7 @@ Widget MDWidget -------- -.. code-block:: +.. code-block:: kv MDWidget: size_hint: .5, None @@ -34,10 +34,15 @@ MDWidget md_bg_color: app.theme_cls.primary_color """ +__all__ = ("MDWidget",) + +from kivy.uix.widget import Widget + from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior -class MDWidget(MDAdaptiveWidget): +class MDWidget(DeclarativeBehavior, MDAdaptiveWidget, Widget): """ See :class:`~kivy.uix.Widget` class documentation for more information. diff --git a/sbapp/kivymd/utils/fitimage.py b/sbapp/kivymd/utils/fitimage.py deleted file mode 100644 index 71d5fea..0000000 --- a/sbapp/kivymd/utils/fitimage.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -FitImage -======== - -.. note:: See :class:`~kivymd.uix.fitimage.FitImage` for more information -""" - -__all__ = ("FitImage",) - -from kivy import Logger - -from kivymd.uix.fitimage import FitImage - -Logger.warning( - "FitImage: Note!" - "\nIn the near future the `FitImage` widget will be moved to the " - "`kivymd.uix.fitimage` package.\nUse import of this widget like this:" - "`from kivymd.uix.fitimage import FitImage`." -)