|
| 1 | +""" |
| 2 | +PageLayout |
| 3 | +========== |
| 4 | +
|
| 5 | +The :class:`PageLayout` class allow to create a simple multiple page |
| 6 | +layout, in a way that allows easy flipping of one page to another using |
| 7 | +borders. |
| 8 | +
|
| 9 | +:class:`PageLayout` doesn't honor size_hint or pos_hint in any way currently. |
| 10 | +
|
| 11 | +.. versionadded:: 1.8.1 |
| 12 | +
|
| 13 | +example:: |
| 14 | +
|
| 15 | + PageLayout: |
| 16 | + Button: |
| 17 | + text: 'page1' |
| 18 | +
|
| 19 | + Button: |
| 20 | + text: 'page2' |
| 21 | +
|
| 22 | + Button: |
| 23 | + text: 'page3' |
| 24 | +""" |
| 25 | + |
| 26 | +__all__ = ('PageLayout', ) |
| 27 | + |
| 28 | +from kivy.uix.layout import Layout |
| 29 | +from kivy.properties import NumericProperty |
| 30 | +from kivy.animation import Animation |
| 31 | + |
| 32 | + |
| 33 | +class PageLayout(Layout): |
| 34 | + '''PageLayout class. See module documentation for more information |
| 35 | + ''' |
| 36 | + |
| 37 | + '''Currently displayed page. |
| 38 | +
|
| 39 | + :data:`page` is a :class:`~kivy.properties.NumericProperty`, default to 0. |
| 40 | + ''' |
| 41 | + page = NumericProperty(0) |
| 42 | + |
| 43 | + '''Width of the border used around current page to display previous/next |
| 44 | + page when needed. |
| 45 | +
|
| 46 | + :data:`border` is a :class:`~kivy.properties.NumericProperty`, |
| 47 | + default to 0. |
| 48 | + ''' |
| 49 | + border = NumericProperty('50dp') |
| 50 | + |
| 51 | + '''Thresold to the swipe action triggering, as percentage of the widget |
| 52 | + size. |
| 53 | +
|
| 54 | + :data:`swipe_threshold` is a :class:`~kivy.properties.NumericProperty`, |
| 55 | + default to .5. |
| 56 | + ''' |
| 57 | + swipe_threshold = NumericProperty(.5) |
| 58 | + |
| 59 | + def __init__(self, **kwargs): |
| 60 | + super(PageLayout, self).__init__(**kwargs) |
| 61 | + |
| 62 | + self.bind( |
| 63 | + border=self._trigger_layout, |
| 64 | + page=self._trigger_layout, |
| 65 | + parent=self._trigger_layout, |
| 66 | + children=self._trigger_layout, |
| 67 | + size=self._trigger_layout, |
| 68 | + pos=self._trigger_layout) |
| 69 | + |
| 70 | + def do_layout(self, *largs): |
| 71 | + l_children = len(self.children) |
| 72 | + for i, c in enumerate(reversed(self.children)): |
| 73 | + if i < l_children: |
| 74 | + width = self.width - self.border |
| 75 | + else: |
| 76 | + width = self.width - 2 * self.border |
| 77 | + |
| 78 | + if i == 0: |
| 79 | + x = self.x |
| 80 | + |
| 81 | + elif i < self.page: |
| 82 | + x = self.x |
| 83 | + |
| 84 | + elif i == self.page: |
| 85 | + x = self.x + self.border |
| 86 | + |
| 87 | + elif i == self.page + 1: |
| 88 | + x = self.right - self.border |
| 89 | + |
| 90 | + else: |
| 91 | + x = self.right |
| 92 | + |
| 93 | + c.height = self.height |
| 94 | + c.width = width |
| 95 | + |
| 96 | + Animation( |
| 97 | + x=x, |
| 98 | + y=self.y, |
| 99 | + d=.5, t='in_quad').start(c) |
| 100 | + |
| 101 | + def on_touch_down(self, touch): |
| 102 | + if self.y < touch.y < self.top: |
| 103 | + if self.page > 0 and self.x < touch.x < (self.x + self.border): |
| 104 | + touch.ud['page'] = 'previous' |
| 105 | + touch.grab(self) |
| 106 | + return True |
| 107 | + |
| 108 | + elif ( |
| 109 | + self.page < len(self.children) - 1 and |
| 110 | + self.right > touch.x > (self.right - self.border) |
| 111 | + ): |
| 112 | + touch.ud['page'] = 'next' |
| 113 | + touch.grab(self) |
| 114 | + return True |
| 115 | + |
| 116 | + return self.children[-self.page - 1].on_touch_down(touch) |
| 117 | + |
| 118 | + def on_touch_move(self, touch): |
| 119 | + if touch.grab_current == self: |
| 120 | + if touch.ud['page'] == 'previous': |
| 121 | + self.children[-self.page - 1].x = max(min( |
| 122 | + self.x + self.border + (touch.x - touch.ox), |
| 123 | + self.right - self.border), |
| 124 | + self.x + self.border) |
| 125 | + |
| 126 | + if self.page > 1: |
| 127 | + self.children[-self.page].x = min( |
| 128 | + self.x + self.border * (touch.sx - touch.osx), |
| 129 | + self.x + self.border) |
| 130 | + |
| 131 | + if self.page < len(self.children) - 1: |
| 132 | + self.children[-self.page + 1].x = min( |
| 133 | + self.right - self.border * (1 - (touch.sx - touch.osx)), |
| 134 | + self.right) |
| 135 | + |
| 136 | + elif touch.ud['page'] == 'next': |
| 137 | + self.children[-self.page + 1].x = min(max( |
| 138 | + self.right - self.border + (touch.x - touch.ox), |
| 139 | + self.x + self.border), |
| 140 | + self.right - self.border) |
| 141 | + |
| 142 | + if self.page >= 1: |
| 143 | + self.children[-self.page - 1].x = max( |
| 144 | + self.x + self.border * (1 - (touch.osx - touch.sx)), |
| 145 | + self.x) |
| 146 | + |
| 147 | + if self.page < len(self.children) - 2: |
| 148 | + self.children[-self.page].x = max( |
| 149 | + self.right + self.border * (touch.sx - touch.osx), |
| 150 | + self.right - self.border) |
| 151 | + |
| 152 | + return self.children[-self.page - 1].on_touch_move(touch) |
| 153 | + |
| 154 | + def on_touch_up(self, touch): |
| 155 | + if touch.grab_current == self: |
| 156 | + if ( |
| 157 | + touch.ud['page'] == 'previous' and |
| 158 | + abs(touch.sx - touch.osx) > self.swipe_threshold |
| 159 | + ): |
| 160 | + self.page -= 1 |
| 161 | + elif ( |
| 162 | + touch.ud['page'] == 'next' and |
| 163 | + abs(touch.sx - touch.osx) > self.swipe_threshold |
| 164 | + ): |
| 165 | + self.page += 1 |
| 166 | + else: |
| 167 | + self._trigger_layout() |
| 168 | + |
| 169 | + touch.ungrab(self) |
| 170 | + return self.children[-self.page + 1].on_touch_up(touch) |
| 171 | + |
| 172 | + |
| 173 | +if __name__ == '__main__': |
| 174 | + from kivy.base import runTouchApp |
| 175 | + from kivy.uix.button import Button |
| 176 | + |
| 177 | + pl = PageLayout() |
| 178 | + for i in range(1, 4): |
| 179 | + b = Button(text='page%s' % i) |
| 180 | + pl.add_widget(b) |
| 181 | + |
| 182 | + runTouchApp(pl) |
0 commit comments