from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
KV = """
<RootWidget>:
# Outer ScrollView (Vertical)
ScrollView:
id: outer_scroll
do_scroll_x: False # Only scroll vertically
do_scroll_y: True
GridLayout:
id: outer_content
cols: 1
size_hint_y: None
height: self.minimum_height # Make content take its needed height
padding: dp(10)
spacing: dp(10)
Label:
text: "Top Content"
size_hint_y: None
height: dp(100)
canvas.before:
Color:
rgba: 0.2, 0.2, 0.8, 0.5
Rectangle:
pos: self.pos
size: self.size
# Inner ScrollView (Horizontal)
ScrollView:
id: inner_scroll
size_hint_y: None # Inner ScrollView needs a defined height
height: dp(120) # Example height for the horizontal scroll area
do_scroll_y: False # CRITICAL: Only scroll horizontally
do_scroll_x: True
#bar_width: dp(10) # Optional: make scrollbar visible
GridLayout:
id: inner_content
rows: 1
size_hint_x: None
width: self.minimum_width # Make content take its needed width
spacing: dp(5)
Button:
text: "Item 1"
size_hint_x: None
width: dp(150)
Button:
text: "Item 2"
size_hint_x: None
width: dp(150)
Button:
text: "Item 3"
size_hint_x: None
width: dp(150)
Button:
text: "Item 4"
size_hint_x: None
width: dp(150)
Button:
text: "Item 5"
size_hint_x: None
width: dp(150)
Button:
text: "Item 6"
size_hint_x: None
width: dp(150)
Label:
text: "Middle Content 1"
size_hint_y: None
height: dp(200)
canvas.before:
Color:
rgba: 0.2, 0.8, 0.2, 0.5
Rectangle:
pos: self.pos
size: self.size
Label:
text: "Middle Content 2"
size_hint_y: None
height: dp(200)
canvas.before:
Color:
rgba: 0.8, 0.2, 0.2, 0.5
Rectangle:
pos: self.pos
size: self.size
Label:
text: "Bottom Content"
size_hint_y: None
height: dp(300) # Make this large enough to ensure vertical scroll
canvas.before:
Color:
rgba: 0.8, 0.8, 0.2, 0.5
Rectangle:
pos: self.pos
size: self.size
"""
class RootWidget(BoxLayout): # This is a BoxLayout, its content is defined in KV
pass
class NestedScrollApp(App):
def build(self):
Builder.load_string(KV) # Load the KV definitions
return RootWidget() # Instantiate and return the root widget
# Kivy will apply the <RootWidget> rule from KV to this instance.
if __name__ == '__main__':
NestedScrollApp().run()
def sv_children(parent: Widget) -> list[Widget]:
svs = []
for chi in parent.children:
svs.extend(sv_children(chi))
if isinstance(chi, ScrollView):
svs.append(chi)
return svs
class EmbeddingScrollView(ScrollView):
def on_touch_down(self, touch: MotionEvent):
inner_scroll_views = sv_children(self)
print(f"on_touch_down on {self.do_scroll_x=} {self.do_scroll_y=} {inner_scroll_views=} of {self=}")
for inner_sv in inner_scroll_views:
coords = inner_sv.to_parent(*inner_sv.to_widget(*touch.pos))
print(f"{inner_sv=} {inner_sv.collide_point(*coords)=} {inner_sv.scroll_x=} {inner_sv.scroll_y=}")
if inner_sv.collide_point(*coords): # and inner_sv.scroll_x not in (1, 0):
touch.push()
touch.apply_transform_2d(inner_sv.to_widget)
touch.apply_transform_2d(inner_sv.to_parent)
consumed = inner_sv.on_touch_down(touch)
touch.pop()
if consumed:
print(" === touch event consumed")
return True
return super().on_touch_down(touch)
```
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
KV = """
RootWidget:
# Outer ScrollView (Vertical)
TracedSV:
id: outer_scroll
tt: "Out"
do_scroll_x: False # Only scroll vertically
do_scroll_y: True
GridLayout:
id: outer_content
cols: 1
size_hint_y: None
height: self.minimum_height # Make content take its needed height
padding: dp(10)
spacing: dp(10)
TracedLab:
text: "Top Label"
tt: "Top"
size_hint_y: None
height: dp(100)
canvas.before:
Color:
rgba: 0.2, 0.2, 0.8, 0.5
Rectangle:
pos: self.pos
size: self.size
# Inner ScrollView (Horizontal)
TracedSV:
id: inner_scroll
tt: "Inn"
size_hint_y: None # Inner ScrollView needs a defined height
height: dp(120) # Example height for the horizontal scroll area
do_scroll_y: False # CRITICAL: Only scroll horizontally
do_scroll_x: True
#bar_width: dp(10) # Optional: make scrollbar visible
GridLayout:
id: inner_content
rows: 1
size_hint_x: None
width: self.minimum_width # Make content take its needed width
spacing: dp(5)
TracedLab:
text: "Left Label"
tt: "Lef"
size_hint_x: None
width: dp(150)
TracedWid:
tt: "Lef"
size_hint_x: None
width: dp(150)
TracedBut:
text: "Left Button"
tt: "Lef"
size_hint_x: None
width: dp(150)
TracedLab:
text: "Right Label"
tt: "Rig"
size_hint_x: None
width: dp(150)
TracedWid:
tt: "Rig"
size_hint_x: None
width: dp(150)
TracedBut:
text: "Right Button"
tt: "Rig"
size_hint_x: None
width: dp(150)
TracedLab:
text: "Middle Label"
tt: "Mid"
size_hint_y: None
height: dp(200)
canvas.before:
Color:
rgba: 0.2, 0.8, 0.2, 0.5
Rectangle:
pos: self.pos
size: self.size
TracedLab:
text: "Bottom Label"
tt: "Btm"
size_hint_y: None
height: dp(600) # Make this large enough to ensure vertical scroll
canvas.before:
Color:
rgba: 0.8, 0.8, 0.2, 0.5
Rectangle:
pos: self.pos
size: self.size
"""
class RootWidget(BoxLayout): # This is a BoxLayout, its content is defined in KV
pass
class TracedSV(ScrollView):
def on_touch_down(self, touch):
ret = super().on_touch_down(touch)
print(f"Scv {self.tt} Dn={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_move(self, touch):
ret = super().on_touch_move(touch)
print(f"Scv {self.tt} Mv={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_up(self, touch):
ret = super().on_touch_up(touch)
print(f"Scv {self.tt} Up={'y' if ret else 'n'} {touch=}")
return ret
class TracedBut(Button):
def on_touch_down(self, touch):
ret = super().on_touch_down(touch)
print(f"But {self.tt} Dn={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_move(self, touch):
ret = super().on_touch_move(touch)
print(f"But {self.tt} Mv={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_up(self, touch):
ret = super().on_touch_up(touch)
print(f"But {self.tt} Up={'y' if ret else 'n'} {touch=}")
return ret
class TracedLab(Label):
def on_touch_down(self, touch):
ret = super().on_touch_down(touch)
print(f"Lab {self.tt} Dn={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_move(self, touch):
ret = super().on_touch_move(touch)
print(f"Lab {self.tt} Mv={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_up(self, touch):
ret = super().on_touch_up(touch)
print(f"Lab {self.tt} Up={'y' if ret else 'n'} {touch=}")
return ret
class TracedWid(Widget):
def on_touch_down(self, touch):
ret = super().on_touch_down(touch)
print(f"Wid {self.tt} Dn={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_move(self, touch):
ret = super().on_touch_move(touch)
print(f"Wid {self.tt} Mv={'y' if ret else 'n'} {touch=}")
return ret
def on_touch_up(self, touch):
ret = super().on_touch_up(touch)
print(f"Wid {self.tt} Up={'y' if ret else 'n'} {touch=}")
return ret
class NestedScrollApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == '__main__':
NestedScrollApp().run()
```
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/kivy-users/371e11e3-8cf3-4dce-848f-93812f038fdcn%40googlegroups.com.