diff --git a/examples/search.py b/examples/search.py index 72653a55c..a00e731f8 100644 --- a/examples/search.py +++ b/examples/search.py @@ -9,7 +9,7 @@ MDScreen: md_bg_color:app.theme_cls.backgroundColor BoxLayout: - padding:[dp(10), dp(50)] + padding:[dp(10), dp(50), dp(10), dp(10)] orientation:"vertical" MDSearchBar: @@ -20,7 +20,8 @@ # Search Bar MDSearchBarLeadingContainer: MDSearchLeadingIcon: - icon:"magnify" + icon: "menu" + on_release: app.open_menu(self) MDSearchBarTrailingContainer: MDSearchTrailingIcon: @@ -39,15 +40,22 @@ icon:"window-close" MDSearchViewContainer: + size_hint_y:1 MDLabel: text:"Hello World!" + halign:"center" Widget: - MDSwitch: - on_active:app.theme_cls.theme_style = "Dark" if app.theme_cls.theme_style == "Light" else "Light" - Widget: - - + BoxLayout: + size_hint_y:None + height:dp(50) + padding:[dp(50), 0] + spacing:dp(10) + MDLabel: + text:"Bar dock" + halign:"center" + MDSwitch: + on_active:search_bar.docked = args[-1] """ class Example(MDApp, CommonApp): diff --git a/kivymd/uix/search/search.kv b/kivymd/uix/search/search.kv index 29ab0eb62..973b9a30c 100644 --- a/kivymd/uix/search/search.kv +++ b/kivymd/uix/search/search.kv @@ -44,6 +44,7 @@ [self.x+self.width, self.y+self.height]] : + size_hint:[1,1] MDBoxLayout: id: root_container orientation: 'vertical' @@ -67,7 +68,8 @@ multiline: False font_size: root._font_style["font-size"] on_focus: if args[-1]: root.switch_state("open") - Widget: + Widget: + id: _wid : size_hint_y: None height: dp(56) diff --git a/kivymd/uix/search/search.py b/kivymd/uix/search/search.py index 00f032c73..e4a804c65 100644 --- a/kivymd/uix/search/search.py +++ b/kivymd/uix/search/search.py @@ -25,6 +25,7 @@ NumericProperty, ObjectProperty, StringProperty, + BooleanProperty ) from kivy.uix.behaviors import ButtonBehavior from kivy.uix.boxlayout import BoxLayout @@ -34,9 +35,9 @@ from kivymd import uix_path from kivymd.font_definitions import theme_font_styles -from kivymd.uix.behaviors import StencilBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.label import MDIcon +from kivymd.utils import next_frame with open( os.path.join(uix_path, "search", "search.kv"), encoding="utf-8" @@ -72,84 +73,121 @@ class MDSearchViewLeadingContainer(BoxLayout): pass -class MDSearchViewContainer(StencilBehavior, BoxLayout): +class MDSearchViewContainer(BoxLayout): pass class MDSearchWidget(RelativeLayout): _font_style = theme_font_styles["Title"]["medium"] - _duration = 0.4 - _transition = "in_out_circ" - state = "closed" + _d = 0.4 + _t = "in_out_circ" + state = "close" def __init__(self, root, *args, **kwargs): super().__init__(*args, **kwargs) self.root = root - def update_pos(self, *args): - self.ids.root_container.pos = self.root.pos + def update_bar(self, *args): + if self.state == "close": + self.ids.root_container.size = self.root.size + self.ids.root_container.radius = dp(28) + self.ids.root_container.pos = self.root.pos + else: + if self.root.docked: + self.ids.root_container.radius = [dp(28)] * 4 + docked_size = self.root.width, dp(56) + self.root.docked_height + self.ids.root_container.pos = [ + self.root.pos[0], self.root.pos[1] - docked_size[1] + dp(56) + ] + else: + self.ids.root_container.radius = [0] * 4 + self.ids.root_container.pos = [0,0] + self.ids.root_container.size = self.size + + def _docked_open(self, opacity_down, opacity_up): + docked_size = self.root.width, dp(56) + self.root.docked_height + Animation( + size=docked_size, + pos=[self.root.pos[0], self.root.pos[1] - docked_size[1] + dp(56)], + radius=[dp(28)] * 4, + t=self._t, + d=self._d, + ).start(self.ids.root_container) + self.root._view_container.size_hint_y = 1 + self.root._view_container.opacity = 0 + self.root._view_container.padding = [0, 0, 0, dp(16)] + next_frame( + self.ids.root_container.add_widget, + self.root._view_container, + index=1, + t=self._d, + ) + next_frame(opacity_up.start, self.root._view_container, t=self._d) + self.icons_open(opacity_up, opacity_down, self._d / 2) - def update_size(self, *args): - self.ids.root_container.size = self.root.size - self.ids.root_container.radius = dp(28) + def _docked_close(self, opacity_down, opacity_up): + self._close(opacity_down, opacity_up) - def _delayed_run(self, delay, func, *args): - Clock.schedule_once(lambda dt: func(*args), delay) + def _open(self, opacity_down, opacity_up): + h_d = self._d / 2 - def _open(self, d, t, opacity_down, opacity_up, docked): + # container self.root._view_container.size_hint_y = 1 self.root._view_container.opacity = 0 + self.root._view_container.padding = [0] * 4 self.ids.root_container.add_widget(self.root._view_container, index=1) - self._delayed_run(d / 4, opacity_up.start, self.root._view_container) - Animation(size=self.size, pos=self.pos, radius=[0] * 4, t=t, d=d).start( - self.ids.root_container - ) - Animation(height=dp(70), t=t, d=d).start(self.ids.header) - opacity_down.start(self.root._bar_trailing_container) - opacity_down.start(self.root._bar_leading_container) - self.root._view_leading_container.opacity = 0 - self.root._view_trailing_container.opacity = 0 - self._delayed_run(d / 2, self.update_state_opened) - self._delayed_run( - d / 2, opacity_up.start, self.root._view_trailing_container - ) - self._delayed_run( - d / 2, opacity_up.start, self.root._view_leading_container - ) + next_frame(opacity_up.start, self.root._view_container, t=h_d / 2) + Animation( + size=self.size, pos=self.pos, radius=[0] * 4, t=self._t, d=self._d + ).start(self.ids.root_container) - def _close(self, d, t, opacity_down, opacity_up, docked): + # header + Animation(height=dp(70), t=self._t, d=self._d).start(self.ids.header) + self.icons_open(opacity_up, opacity_down, h_d) + + def _close(self, opacity_down, opacity_up): + h_d = self._d / 2 + # container self.root._view_container.size_hint_y = None self.root._view_container.opacity = 1 opacity_down.start(self.root._view_container) if self.root._view_container in self.ids.root_container.children: - self._delayed_run( - d / 2, + next_frame( self.ids.root_container.remove_widget, self.root._view_container, + t=h_d, ) - self._delayed_run( - d, setattr, self.root._view_container, "height", dp(55) - ) + next_frame(setattr, self.root._view_container, "height", dp(55), t=h_d) Animation( size=[self.root.width, dp(56)], pos=self.root.pos, radius=[dp(28)] * 4, - t=t, - d=d, + t=self._t, + d=self._d, ).start(self.ids.root_container) - Animation(height=dp(56), t=t, d=d).start(self.ids.header) + + # header + Animation(height=dp(56), t=self._t, d=self._d).start(self.ids.header) + self.icons_close(opacity_up, opacity_down, h_d) + + def icons_close(self, opacity_up, opacity_down, h_d): opacity_down.start(self.root._view_trailing_container) opacity_down.start(self.root._view_leading_container) self.root._bar_leading_container.opacity = 0 self.root._bar_trailing_container.opacity = 0 - self._delayed_run(d / 2, self.update_state_closed) - self._delayed_run( - d / 2, opacity_up.start, self.root._bar_trailing_container - ) - self._delayed_run( - d / 2, opacity_up.start, self.root._bar_leading_container - ) + next_frame(self.update_state_closed, t=h_d) + next_frame(opacity_up.start, self.root._bar_trailing_container, t=h_d) + next_frame(opacity_up.start, self.root._bar_leading_container, t=h_d) + + def icons_open(self, opacity_up, opacity_down, h_d): + opacity_down.start(self.root._bar_trailing_container) + opacity_down.start(self.root._bar_leading_container) + self.root._view_leading_container.opacity = 0 + self.root._view_trailing_container.opacity = 0 + next_frame(self.update_state_opened, t=h_d) + next_frame(opacity_up.start, self.root._view_trailing_container, t=h_d) + next_frame(opacity_up.start, self.root._view_leading_container, t=h_d) switching_state = False @@ -157,19 +195,23 @@ def switch_state(self, new_state): if self.switching_state or new_state == self.state: return self.switching_state = True - docked = False - opacity_down = Animation( - opacity=0, t=self._transition, d=self._duration / 2 - ) - opacity_up = Animation( - opacity=1, t=self._transition, d=self._duration / 2 - ) - getattr(self, "_" + new_state)( - self._duration, self._transition, opacity_down, opacity_up, docked - ) + opacity_down = Animation(opacity=0, t=self._t, d=self._d / 2) + opacity_up = Animation(opacity=1, t=self._t, d=self._d / 2) + + if self.root.docked: + self.root.width = self.root.docked_width + self.ids.root_container.width = self.root.docked_width + getattr(self, "_docked_" + new_state)(opacity_down, opacity_up) + else: + getattr(self, "_" + new_state)(opacity_down, opacity_up) + + if new_state == "close": + self.ids.text_input.focus = False + + self.state = new_state Clock.schedule_once( - lambda dt: setattr(self, "switching_state", False), self._duration + lambda dt: setattr(self, "switching_state", False), self._d ) def init_state(self): @@ -181,6 +223,11 @@ def clean_header(self): self.ids.header.remove_widget(child) def init_state(self): + if self.root.docked: + self.root.size_hint_x = None + self.root.width = self.root.docked_width + else: + self.root.size_hint_x = 1 self.ids.root_container.size = [self.root.width, dp(56)] self.update_state_closed() @@ -200,6 +247,9 @@ class MDSearchBar(Widget): leading_icon = StringProperty("magnify") supporting_text = StringProperty("Hinted search text") view_root = ObjectProperty(None) + docked_width = NumericProperty(dp(360)) + docked_height = NumericProperty(dp(240)) + docked = BooleanProperty(False) # internal props _search_widget = None @@ -219,8 +269,16 @@ class MDSearchBar(Widget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._search_widget = MDSearchWidget(self) - self.bind(pos=self._search_widget.update_pos) - self.bind(size=self._search_widget.update_size) + self.bind(pos=self._search_widget.update_bar) + self.bind(size=self._search_widget.update_bar) + self.on_docked(self, self.docked) + + def on_docked(self, instance, docked): + if docked: + self.size_hint_x = None + self.width = self.docked_width + else: + self.size_hint_x = 1 def on_supporting_text(self, instance, text): self._search_widget.ids.text_input.hint_text = text @@ -230,6 +288,8 @@ def on_view_root(self, *args): self._search_widget.parent.remove_widget(self._search_widget) self.view_root.add_widget(self._search_widget) self._search_widget.init_state() + self._search_widget.update_bar() + self.view_root.bind(size=self._search_widget.update_bar) def add_widget(self, widget): if widget.__class__.__name__ in self._view_map.keys(): diff --git a/kivymd/utils/__init__.py b/kivymd/utils/__init__.py index e69de29bb..77dc8f54c 100755 --- a/kivymd/utils/__init__.py +++ b/kivymd/utils/__init__.py @@ -0,0 +1,8 @@ +from kivy.clock import Clock + +def next_frame(func, *args, **kwargs): + if time := kwargs.get("t"): + del kwargs["t"] + return Clock.schedule_once(lambda _: func(*args, **kwargs), time) + else: + return Clock.schedule_once(lambda _: func(*args, **kwargs))