kivymd / KivyMD

KivyMD is a collection of Material Design compliant widgets for use with Kivy, a framework for cross-platform, touch-enabled graphical applications. https://youtube.com/c/KivyMD https://twitter.com/KivyMD https://habr.com/ru/users/kivymd https://stackoverflow.com/tags/kivymd
https://kivymd.readthedocs.io
MIT License
2.23k stars 672 forks source link

MDTextField custom radius ? #1492

Closed jhay06 closed 1 year ago

jhay06 commented 1 year ago

Hi , would like to ask how can i achieve to set a custom radius in mdtextfield , from documentation there's no available properties where i can set the corner radius ,

Thanks Regards

HeaTTheatR commented 1 year ago

What type of text field exactly are you asking for?

jhay06 commented 1 year ago

rectangle or round

jbsidis commented 1 year ago

Hello Jhay06, You can create that radius effect or apply it by combining a few specific details, in canvas instructions, refer to the code below:

For radius dp(15): image

For radius dp(9): image

You have to add a rounded rectangle with a line that fits in the rectangle:

Kivy Lang class:

`Screen: NavigationLayout: id: jbsidis ScreenManager: id: J Screen: name: "home"

TopToolBarBlue

        FloatLayout:
            id: real_  
            BoxLayout:
                id: f1top
                pos_hint: {"center_x": .5, "center_y": 1.38}
                padding: dp(15)
                canvas.before:
                    Color:
                        rgba: [0.1,0.5,0.7,1]
                    Rectangle:
                        pos: self.pos[0],self.pos[1]+dp(35)
                        size: self.size[0]+dp(10), dp(50)

                canvas.after:
                    Color:
                        rgba: [0,0,0,.3]
                    Line:
                        width: dp(1)
                        rectangle:
                            (self.x, self.y+dp(34), self.width+dp(15), dp(0))        

                BoxLayout:

                    FloatLayout:
                        size_hint: .1,None
                        BoxLayout:
                            id: coe
                            pos_hint: {"center_x": .45, "center_y": .80}
                            padding: dp(10),dp(-5)
                            size_hint: .3, None
                            orientation: "vertical"
                            #White rectangle in search field
                            canvas.before:
                                Color:
                                    rgba: [1,1,1,1]
                                RoundedRectangle:
                                    pos: self.pos[0]-dp(50),self.pos[1]-dp(4)
                                    size: self.width+dp(90), dp(29)
                                    radius: [dp(9),dp(9),dp(9),dp(9)]

                            #red rectangular 
                            canvas.after:
                                Color:
                                    rgba: [0.1,0.1,0.9,1] #[0.1921,0.1921,0.2078,1]
                                Line:
                                    width: dp(1)
                                    rounded_rectangle:
                                        (self.x-dp(50), self.y-dp(4), self.width+dp(90), dp(29),\
                                        dp(9),dp(9),dp(9),dp(9),\
                                        dp(50))

                        MDIconButton:
                            icon: "magnify"
                            ripple_scale: .1
                            #user_font_size: "17sp"
                            theme_text_color: "Custom"
                            text_color: [0,0,0,.6]
                            md_bg_color: [0,0,0,0]
                            pos_hint: {"center_x": .275, "center_y": .38}
                            on_release:
                                root.ids.rs23.focus=True
                        BoxLayout:
                            id: co
                            pos_hint: {"center_x": .37, "center_y": .68}
                            padding: dp(10),dp(-5)
                            size_hint: .15, None
                            orientation: "horizontal"
                            MDTextFieldx:
                                id: rs23
                                fill_color: 0, 0, 0, 1
                                line_color_normal: 0,0,0,1
                                line_color_focus: 0,0,0,1
                                size_hint: None, None
                                markup: True
                                hint_text: "   JBSIDIS"
                                on_text:
                                    self.b12(self.text)
                        FloatLayout:
                            id: wel7
                            MDIconButton:
                                size_hint: .36,.3
                                ripple_scale: .1
                                no_ripple_effect: True
                                pos_hint: {"center_x": .46, "center_y": 6.75}
                                icon: "a.png"
                                on_release: root.ids.rs23.focus=True
size_hint_x: .5 width: self.texture_size[0] shorten: True shorten_from: "right" : canvas.before: Clear Color: rgba: self._current_error_color Rectangle: texture: self._msg_lbl.texture size: self._msg_lbl.texture_size[0] - (dp(3) if root.mode in ("fill", "rectangle") else 0), \ self._msg_lbl.texture_size[1] - (dp(3) if root.mode in ("fill", "rectangle") else 0) pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + (dp(3) if root.mode in ("fill", "rectangle") else 0) Color: rgba: ((0, 0, 1, 1) if self.focus and not \ self._cursor_blink else (0, 0, 0, 0)) Rectangle: pos: (int(x) for x in self.cursor_pos) size: 2, -self.line_height Color: rgba: [.46666,.5294,.5294,1] Rectangle: texture: self._hint_lbl.texture size: self._hint_lbl.texture_size pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + self.height - dp(40) Color: rgba: self.disabled_foreground_color if self.disabled else\ (self.hint_text_color if not self.text and not\ self.focus else self.foreground_color) Color: rgba: self._current_line_color Line: width: dp(1) if root.mode == "rectangle" else dp(0.00001) points: ( self.x + root._line_blank_space_right_point, self.top - self._hint_lbl.texture_size[1] // 2, self.right + dp(12), self.top - self._hint_lbl.texture_size[1] // 2, self.right + dp(12), self.y, self.x - dp(12), self.y, self.x - dp(12), self.top - self._hint_lbl.texture_size[1] // 2, self.x + root._line_blank_space_left_point, self.top - self._hint_lbl.texture_size[1] // 2 ) canvas.after: Color: rgba: root.fill_color if root.mode == "fill" else (0, 0, 0, 0) RoundedRectangle: pos: self.x, self.y size: self.width - dp(13), self.height - dp(8) radius: (dp(8),dp(8),dp(8),dp(8),dp(8)) font_name: "Roboto" if not root.font_name else root.font_name foreground_color: app.theme_cls.text_color font_size: "18sp" bold: False padding: 0 if root.mode != "fill" else "8dp", \ "16dp" if root.mode != "fill" else "24dp", \ 0 if root.mode != "fill" and not root.icon_right else ("14dp" if not root.icon_right else self._lbl_icon_right.texture_size[1] + dp(20)), \ "16dp" if root.mode == "fill" else "10dp" multiline: False size_hint_y: None height: self.minimum_height + (dp(8) if root.mode != "fill" else 0) ` Python code for kivymd and imports: ` from kivymd.uix.behaviors import ( RectangularElevationBehavior, BackgroundColorBehavior, RectangularRippleBehavior ) from kivymd.uix.behaviors import ( SpecificBackgroundColorBehavior, RectangularElevationBehavior, CircularElevationBehavior ) from kivymd.theming import ThemableBehavior from kivy.uix.behaviors import ButtonBehavior import sys sys.path.append("/".join(x for x in __file__.split("/")[:-1])) from kivy.properties import ( BoundedNumericProperty, ListProperty, OptionProperty, ObjectProperty, StringProperty, NumericProperty, BooleanProperty, ReferenceListProperty, ) from kivy.animation import Animation from kivy.uix.widget import Widget from kivy.utils import get_color_from_hex from kivy.uix.widget import Widget from kivymd.font_definitions import theme_font_styles from kivymd.theming import ThemableBehavior from kivymd.uix.label import MDIcon from kivymd.color_definitions import hue, palette, text_colors from kivy.uix.textinput import TextInput from kivy.uix.label import Label from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import RectangularElevationBehavior from kivymd.font_definitions import theme_font_styles from kivymd.uix.behaviors import ( SpecificBackgroundColorBehavior, RectangularElevationBehavior, ) from kivymd.uix.boxlayout import MDBoxLayout from kivy.metrics import dp from kivy.metrics import sp from kivy.clock import Clock from kivy.uix.boxlayout import BoxLayout from kivy.uix.floatlayout import FloatLayout from kivymd.app import MDApp from kivy.core.window import Window from kivy.lang import Builder DEVICE_TYPE ="desktop" #All classes for subset class TextfieldLabel(ThemableBehavior, Label): font_style = OptionProperty("Body1", options=theme_font_styles) # field = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) self.font_size = sp(self.theme_cls.font_styles[self.font_style][1]) class MDTextFieldx(RectangularElevationBehavior, ThemableBehavior, TextInput): helper_text = StringProperty("This field is required") helper_text_mode = OptionProperty( "none", options=["none", "on_error", "persistent", "on_focus"] ) max_text_length = NumericProperty(None) required = BooleanProperty(False) color_mode = OptionProperty( "accent", options=["primary", "accent", "custom"] ) mode = OptionProperty("line", options=["rectangle", "fill"]) line_color_normal = ListProperty() line_color_focus = ListProperty() error_color = ListProperty() fill_color = ListProperty((0, 0, 0, 0)) active_line = BooleanProperty(True) error = BooleanProperty(False) current_hint_text_color = ListProperty() icon_right = StringProperty() icon_right_color = ListProperty((0, 0, 0, 1)) _text_len_error = BooleanProperty(False) _hint_lbl_font_size = NumericProperty("16sp") _line_blank_space_right_point = NumericProperty(0) _line_blank_space_left_point = NumericProperty(0) _hint_y = NumericProperty("38dp") _line_width = NumericProperty(0) _current_line_color = ListProperty((0, 0, 0, 0)) _current_error_color = ListProperty((0, 0, 0, 0)) _current_hint_text_color = ListProperty((0, 0, 0, 0)) _current_right_lbl_color = ListProperty((0, 0, 0, 0)) _msg_lbl = None _right_msg_lbl = None _hint_lbl = None _lbl_icon_right = None def __init__(self, **kwargs): self.set_objects_labels() super().__init__(**kwargs) # Sets default colors. self.line_color_normal = self.theme_cls.divider_color self.line_color_focus = self.theme_cls.primary_color self.error_color = self.theme_cls.error_color self._current_hint_text_color = self.theme_cls.disabled_hint_text_color self._current_line_color = self.theme_cls.primary_color self.bind( helper_text=self._set_msg, hint_text=self._set_hint, _hint_lbl_font_size=self._hint_lbl.setter("font_size"), helper_text_mode=self._set_message_mode, max_text_length=self._set_max_text_length, text=self.on_text, ) self.theme_cls.bind( primary_color=self._update_primary_color, theme_style=self._update_theme_style, accent_color=self._update_accent_color, ) self.has_had_text = False def set_objects_labels(self): """Creates labels objects for the parameters `helper_text`,`hint_text`, etc.""" # Label object for `helper_text` parameter. self._msg_lbl = TextfieldLabel( font_style="Caption", halign="left", valign="middle", text=self.helper_text, field=self, ) # Label object for `max_text_length` parameter. self._right_msg_lbl = TextfieldLabel( font_style="Caption", halign="right", valign="middle", text="", field=self, ) # Label object for `hint_text` parameter. self._hint_lbl = TextfieldLabel( font_style="Subtitle1", halign="left", valign="middle", field=self ) # MDIcon object for the icon on the right. self._lbl_icon_right = MDIcon(theme_text_color="Custom") def on_icon_right(self, instance, value): self._lbl_icon_right.icon = value def on_icon_right_color(self, instance, value): self._lbl_icon_right.text_color = value def on_width(self, instance, width): """Called when the application window is resized.""" if ( any((self.focus, self.error, self._text_len_error)) and instance is not None ): # Bottom line width when active focus. self._line_width = width self._msg_lbl.width = self.width self._right_msg_lbl.width = self.width def b12(self,text): if text=="": self._hint_lbl.text = " Search" else: self._hint_lbl.text = "" def clearing_HT(self): self._hint_lbl.text = "" def clearing_HT2(self): self._hint_lbl.text = " Search in MSX" def on_focus(self, *args): disabled_hint_text_color = self.theme_cls.disabled_hint_text_color Animation.cancel_all( self, "_line_width", "_hint_y", "_hint_lbl_font_size" ) self._set_text_len_error() if self.focus: self._line_blank_space_right_point = ( self._get_line_blank_space_right_point() ) _fill_color = self.fill_color _fill_color[3] = self.fill_color[3] - 0.1 Animation( _line_blank_space_right_point=self._line_blank_space_right_point, _line_blank_space_left_point=self._hint_lbl.x - dp(5), _current_hint_text_color=self.line_color_focus, fill_color=_fill_color, duration=0.2, t="out_quad", ).start(self) Clock.schedule_once(lambda x:self.clearing_HT(),0.2) self.has_had_text = True Animation.cancel_all( self, "_line_width", "_hint_y", "_hint_lbl_font_size" ) if not self.text: Clock.schedule_once(lambda x:self.clearing_HT2(),0.2) self._anim_lbl_font_size(dp(14), sp(12)) Animation(_line_width=self.width, duration=0.2, t="out_quad").start( self ) if self._get_has_error(): self._anim_current_error_color(self.error_color) if self.helper_text_mode == "on_error" and ( self.error or self._text_len_error ): self._anim_current_error_color(self.error_color) elif ( self.helper_text_mode == "on_error" and not self.error and not self._text_len_error ): self._anim_current_error_color((0, 0, 0, 0)) elif self.helper_text_mode in ("persistent", "on_focus"): self._anim_current_error_color(disabled_hint_text_color) else: self._anim_current_right_lbl_color(disabled_hint_text_color) Animation(duration=0.2, color=self.line_color_focus).start( self._hint_lbl ) if self.helper_text_mode == "on_error": self._anim_current_error_color((0, 0, 0, 0)) if self.helper_text_mode in ("persistent", "on_focus"): self._anim_current_error_color(disabled_hint_text_color) else: _fill_color = self.fill_color _fill_color[3] = self.fill_color[3] + 0.1 Animation(fill_color=_fill_color, duration=0.2, t="out_quad").start( self ) if not self.text: self._anim_lbl_font_size(dp(38), sp(16)) Animation( _line_blank_space_right_point=0, _line_blank_space_left_point=0, duration=0.2, t="out_quad", ).start(self) if self._get_has_error(): self._anim_get_has_error_color(self.error_color) if self.helper_text_mode == "on_error" and ( self.error or self._text_len_error ): self._anim_current_error_color(self.error_color) elif ( self.helper_text_mode == "on_error" and not self.error and not self._text_len_error ): self._anim_current_error_color((0, 0, 0, 0)) elif self.helper_text_mode == "persistent": self._anim_current_error_color(disabled_hint_text_color) elif self.helper_text_mode == "on_focus": self._anim_current_error_color((0, 0, 0, 0)) else: Animation(duration=0.2, color=(1, 1, 1, 1)).start( self._hint_lbl ) self._anim_get_has_error_color() if self.helper_text_mode == "on_error": self._anim_current_error_color((0, 0, 0, 0)) elif self.helper_text_mode == "persistent": self._anim_current_error_color(disabled_hint_text_color) elif self.helper_text_mode == "on_focus": self._anim_current_error_color((0, 0, 0, 0)) Animation(_line_width=0, duration=0.2, t="out_quad").start(self) def on_text(self, instance, text): if len(text) > 0: self.has_had_text = True if self.max_text_length is not None: self._right_msg_lbl.text = f"{len(text)}/{self.max_text_length}" self._set_text_len_error() if self.error or self._text_len_error: if self.focus: self._anim_current_line_color(self.error_color) if self.helper_text_mode == "on_error" and ( self.error or self._text_len_error ): self._anim_current_error_color(self.error_color) if self._text_len_error: self._anim_current_right_lbl_color(self.error_color) else: if self.focus: self._anim_current_right_lbl_color( self.theme_cls.disabled_hint_text_color ) self._anim_current_line_color(self.line_color_focus) if self.helper_text_mode == "on_error": self._anim_current_error_color((0, 0, 0, 0)) if len(self.text) != 0 and not self.focus: self._hint_y = dp(14) self._hint_lbl_font_size = sp(12) def on_text_validate(self): self.has_had_text = True self._set_text_len_error() def on_color_mode(self, instance, mode): if mode == "primary": self._update_primary_color() elif mode == "accent": self._update_accent_color() elif mode == "custom": self._update_colors(self.line_color_focus) def on_line_color_focus(self, *args): if self.color_mode == "custom": self._update_colors(self.line_color_focus) def on__hint_text(self, instance, value): pass def _anim_get_has_error_color(self, color=None): # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_get_has_error.png if not color: line_color = self.line_color_focus hint_text_color = self.theme_cls.disabled_hint_text_color right_lbl_color = (0, 0, 0, 0) else: line_color = color hint_text_color = color right_lbl_color = color Animation( duration=0.2, _current_line_color=line_color, _current_hint_text_color=hint_text_color, _current_right_lbl_color=right_lbl_color, ).start(self) def _anim_current_line_color(self, color): # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_line_color.gif Animation( duration=0.2, _current_hint_text_color=color, _current_line_color=color, ).start(self) def _anim_lbl_font_size(self, hint_y, font_size): Animation( _hint_y=hint_y, _hint_lbl_font_size=font_size, duration=0.2, t="out_quad", ).start(self) def _anim_current_right_lbl_color(self, color, duration=0.2): # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_right_lbl_color.png Animation(duration=duration, _current_right_lbl_color=color).start(self) def _anim_current_error_color(self, color, duration=0.2): # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_disabled_color.gif # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_fade.gif Animation(duration=duration, _current_error_color=color).start(self) def _update_colors(self, color): self.line_color_focus = color if not self.error and not self._text_len_error: self._current_line_color = color if self.focus: self._current_line_color = color def _update_accent_color(self, *args): if self.color_mode == "accent": self._update_colors(self.theme_cls.accent_color) def _update_primary_color(self, *args): if self.color_mode == "primary": self._update_colors(self.theme_cls.primary_color) #self.theme_cls.primary_color def _update_theme_style(self, *args): self.line_color_normal = self.theme_cls.divider_color if not any([self.error, self._text_len_error]): if not self.focus: self._current_hint_text_color = ( self.theme_cls.disabled_hint_text_color ) self._current_right_lbl_color = ( self.theme_cls.disabled_hint_text_color ) if self.helper_text_mode == "persistent": self._current_error_color = ( self.theme_cls.disabled_hint_text_color ) def _get_has_error(self): if self.error or all( [ self.max_text_length is not None and len(self.text) > self.max_text_length ] ): has_error = True else: if all((self.required, len(self.text) == 0, self.has_had_text)): has_error = True else: has_error = False return has_error def _get_line_blank_space_right_point(self): # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_line_blank_space_right_point.png return ( self._hint_lbl.texture_size[0] - self._hint_lbl.texture_size[0] / 100 * dp(18) if DEVICE_TYPE == "desktop" else dp(10) ) def _get_max_text_length(self): """Returns the maximum number of characters that can be entered in a text field.""" return ( sys.maxsize if self.max_text_length is None else self.max_text_length ) def _set_text_len_error(self): if len(self.text) > self._get_max_text_length() or all( (self.required, len(self.text) == 0, self.has_had_text) ): self._text_len_error = True else: self._text_len_error = False def _set_hint(self, instance, text): self._hint_lbl.text = text def _set_msg(self, instance, text): self._msg_lbl.text = text self.helper_text = text def _set_message_mode(self, instance, text): self.helper_text_mode = text if self.helper_text_mode == "persistent": self._anim_current_error_color( self.theme_cls.disabled_hint_text_color, 0.1 ) def _set_max_text_length(self, instance, length): self.max_text_length = length self._right_msg_lbl.text = f"{len(self.text)}/{length}" def _refresh_hint_text(self): pass ` Using this code snippets, you will see something like: ![image](https://github.com/kivymd/KivyMD/assets/68297577/c2aceda7-9ce6-47e6-b790-62a312f9a626) Hope this helps, Regards,
jhay06 commented 1 year ago

isn't it too long ? hoping that we can set radius in default mdtextfield without customizing code