Пример #1
0
    def _create_ui(self):
        # Title Text
        if self.titletext is not None:
            self.title = Label(font_size=18,
                               bold=True,
                               anchor_x='center',
                               anchor_y='center',
                               label=self.titletext)
            self.title.x = self.width / 2 + self.x
            self.title.y = self.height - 20 + self.y

        # Delete Button
        if self.deletable:
            self.db = MTToggleButton(label='X',
                                     pos=(self.x + self.width - 80,
                                          self.y + self.height - 40),
                                     size=(80, 40),
                                     cls='kineticlist-delete')
            self.db.push_handlers(on_press=self.toggle_delete)
            self.widgets.append(self.db)

        # Search Button and Input Text Area
        if self.searchable:
            self.sb = MTToggleButton(
                label='S',  #Button
                pos=(self.x, self.y + self.width - 40),
                size=(80, 40),
                cls='kineticlist-search')

            self.sb.push_handlers(on_press=self.toggle_search)
            self.sb.parent = self
            self.widgets.append(self.sb)

            self.sinput = pymt.MTTextInput(pos=(self.x,
                                                self.y + self.height - 40),
                                           size=(80, 40),
                                           style={'font-size': 20})
            self.sinput.parent = self
            self.sinput.push_handlers(on_text_change=self.apply_filter)
            self.widgets.insert(0, self.sinput)

            # Animations to hide and show the search text input box
            self._a_sinput_in = Animation(y=self.y + self.height - 40 -
                                          self.sinput.size[1],
                                          duration=0.5,
                                          f='ease_out_cubic')
            self._a_sinput_out = Animation(y=self.y + self.height -
                                           self.sinput.size[1],
                                           duration=0.5,
                                           f='ease_out_cubic')
Пример #2
0
    def __init__(self, **kwargs):
        kwargs.setdefault('friction', 10)
        kwargs.setdefault('padding_x', 4)
        kwargs.setdefault('padding_y', 4)
        kwargs.setdefault('w_limit', 1)
        kwargs.setdefault('h_limit', 0)
        kwargs.setdefault('do_x', False)
        kwargs.setdefault('do_y', True)
        kwargs.setdefault('title', 'No title')
        kwargs.setdefault('deletable', True)
        kwargs.setdefault('searchable', True)
        kwargs.setdefault('trigger_distance', 3)
        kwargs.setdefault('align', 'center')

        super(MTKineticList, self).__init__(**kwargs)

        self.register_event_type('on_delete')

        self._a_sinput_out  = None
        self._a_sinput_in   = None
        self.title          = Label('')
        self.sb             = None
        self.sinput         = None

        self.do_x       = kwargs.get('do_x')
        self.do_y       = kwargs.get('do_y')
        self.titletext  = kwargs.get('title')
        self.deletable  = kwargs.get('deletable')
        self.searchable = kwargs.get('searchable')
        self.friction   = kwargs.get('friction')
        self.padding_x  = kwargs.get('padding_x')
        self.padding_y  = kwargs.get('padding_y')
        self.w_limit    = kwargs.get('w_limit')
        self.h_limit    = kwargs.get('h_limit')
        self.align      = kwargs.get('align')
        self.trigger_distance = kwargs.get('trigger_distance')

        if self.w_limit and self.h_limit:
            raise Exception('You cannot limit both axes')
        elif not(self.w_limit or self.h_limit):
            raise Exception('You must limit at least one axis')

        # How far to offset tself.deletable and he axes(used for scrolling/panning)
        self.xoffset = 0
        self.yoffset = 0
        # X and Y translation vectors for the kinetic movement
        self.vx = 0
        self.vy = 0
        # List of all children, whatever will be the search
        self.pchildren = []
        # For extra blob stats
        self.touch = {}
        # Holds widgets not a part of the scrolling(search button, etc)
        self.widgets = []
        self._last_content_size = 0
        self._scrollbar_index = 0
        self._scrollbar_size = 0

        # create the UI part.
        self._create_ui()
Пример #3
0
 def create_line_label(self, text):
     '''Create a label from a text, using line options
     '''
     ntext = text.replace('\n', '').replace('\t', ' ' * self.tab_width)
     kw = self.get_line_options()
     cid = '%s\0%s' % (ntext, str(kw))
     label = Cache.get('textarea.label', cid)
     if not label:
         label = Label(ntext, **kw)
         Cache.append('textarea.label', cid, label)
     return label
Пример #4
0
    def _create_ui(self):
        # Title Text
        if self.titletext is not None:
            self.title = Label(
                font_size=18,
                bold=True,
                anchor_x='center',
                anchor_y='center',
                label=self.titletext)
            self.title.x = self.width/2 + self.x
            self.title.y = self.height - 20 + self.y

        # Delete Button
        if self.deletable:
            self.db = MTToggleButton(
                label='X',
                pos=(self.x + self.width - 80, self.y + self.height - 40),
                size=(80, 40),
                cls='kineticlist-delete')
            self.db.push_handlers(on_press=self.toggle_delete)
            self.widgets.append(self.db)

        # Search Button and Input Text Area
        if self.searchable:
            self.sb = MTToggleButton(
                label='S',  #Button
                pos=(self.x, self.y + self.width - 40),
                size=(80, 40),
                cls='kineticlist-search')

            self.sb.push_handlers(on_press=self.toggle_search)
            self.sb.parent = self
            self.widgets.append(self.sb)

            self.sinput = pymt.MTTextInput(pos=
                (self.x, self.y + self.height - 40), size=(80, 40),
                style={'font-size': 20})
            self.sinput.parent = self
            self.sinput.push_handlers(on_text_change=self.apply_filter)
            self.widgets.insert(0, self.sinput)

            # Animations to hide and show the search text input box
            self._a_sinput_in = Animation(y=self.y + self.height - 40 -
                                         self.sinput.size[1], duration=0.5,
                                         f='ease_out_cubic')
            self._a_sinput_out = Animation(y=self.y + self.height -
                                          self.sinput.size[1], duration=0.5,
                                         f='ease_out_cubic')
Пример #5
0
Файл: label.py Проект: imc/pymt
    def _set_label(self, label):
        self._label = label
        opts = {}
        opts['anchor_y'] = 'bottom'
        if self.style.get('font-name') != '':
            opts['font_name'] = self.style.get('font-name')
        if int(self.style.get('font-size')) > 0:
            opts['font_size'] = int(self.style.get('font-size'))
        if self.style.get('font-weight'):
            if self.style.get('font-weight') in ['italic', 'bolditalic']:
                opts['italic'] = True
            if self.style.get('font-weight') in ['bold', 'bolditalic']:
                opts['bold'] = True
        opts['color'] = map(lambda x: int(x * 255), self.style.get('color'))
        opts['text'] = label
        if self.multiline:
            opts['multiline'] = self.multiline
            opts['width'] = self.width

        self._label_obj = Label(label=label, **opts)
        self.size = self._label_obj.content_width, self._label_obj.content_height
Пример #6
0
class MTKineticList(MTStencilContainer):
    '''This is a kinetic container widget, that allows you to make
    a kinetic list scrolling in either direction.

    :Parameters:
        `align` : string, default to 'center'
            Alignement of widget inside the row (or col). Can be
            one of 'center', 'left', 'right'
        `friction` : float, defaults to 10
            The Pseudo-friction of the pseudo-kinetic scrolling.
            Formula for friction is ::

                acceleration = 1 + friction * frame_delta_time

        `padding_x` : int, defaults to 4
            The spacing between scrolling items on the x axis
        `padding_y` : int, defaults to 4
            The spacing between scrolling items on the y axis
        `w_limit` : int, defaults to 1
            The limit of items that will appear horizontally.
            When this is set to a non-zero value the width(in
            terms of items in the kinetic list) will be w_limit,
            and the height will continually expand.
        `h_limit` : int, defaults to 0
            Exect opposite of w_limit.  If I didn't make either
            this or w_limit clear go bug xelapond
        `do_x` : bool, defaults to False
            Enable scrolling on the X axis
        `do_y` : bool, defaults to True
            Enable scrolling on the Y axis
        `title` : string, defaults to <Title Goes Here>
            Sets the title of the widget, which appears in 20
            point font at the top
        `deletable` : bool, defaults to True
            When enabled it allows you to delete children by
            entering delete mode(red button in upper left)
        `searchable` : bool, defaults to True
            When enabled it allows you to enter search mode
            and filter items
        `trigger_distance` : int, default to 3
            Maximum trigger distance to dispatch event on children
            (this mean if you move too much, trigger will not happen.)

    :Styles:
        `bg-color` : color
             Background color of the widget
        `scrollbar-size` : int
            Size of scrollbar in pixel (use 0 to disable it.)
        `scrollbar-color` : color
            Color of scrollbar
        `scrollbar-margin` : int int int int
            Margin top/right/bottom/left of scrollbar (left are not used.)

    :Events:
        `on_delete` (child)
            Fired when an item gets deleted.
    '''
    def __init__(self, **kwargs):
        kwargs.setdefault('friction', 10)
        kwargs.setdefault('padding_x', 4)
        kwargs.setdefault('padding_y', 4)
        kwargs.setdefault('w_limit', 1)
        kwargs.setdefault('h_limit', 0)
        kwargs.setdefault('do_x', False)
        kwargs.setdefault('do_y', True)
        kwargs.setdefault('title', 'No title')
        kwargs.setdefault('deletable', True)
        kwargs.setdefault('searchable', True)
        kwargs.setdefault('trigger_distance', 3)
        kwargs.setdefault('align', 'center')

        super(MTKineticList, self).__init__(**kwargs)

        self.register_event_type('on_delete')

        self._a_sinput_out  = None
        self._a_sinput_in   = None
        self.title          = Label('')
        self.sb             = None
        self.sinput         = None

        self.do_x       = kwargs.get('do_x')
        self.do_y       = kwargs.get('do_y')
        self.titletext  = kwargs.get('title')
        self.deletable  = kwargs.get('deletable')
        self.searchable = kwargs.get('searchable')
        self.friction   = kwargs.get('friction')
        self.padding_x  = kwargs.get('padding_x')
        self.padding_y  = kwargs.get('padding_y')
        self.w_limit    = kwargs.get('w_limit')
        self.h_limit    = kwargs.get('h_limit')
        self.align      = kwargs.get('align')
        self.trigger_distance = kwargs.get('trigger_distance')

        if self.w_limit and self.h_limit:
            raise Exception('You cannot limit both axes')
        elif not(self.w_limit or self.h_limit):
            raise Exception('You must limit at least one axis')

        # How far to offset tself.deletable and he axes(used for scrolling/panning)
        self.xoffset = 0
        self.yoffset = 0
        # X and Y translation vectors for the kinetic movement
        self.vx = 0
        self.vy = 0
        # List of all children, whatever will be the search
        self.pchildren = []
        # For extra blob stats
        self.touch = {}
        # Holds widgets not a part of the scrolling(search button, etc)
        self.widgets = []
        self._last_content_size = 0
        self._scrollbar_index = 0
        self._scrollbar_size = 0

        # create the UI part.
        self._create_ui()

    def _create_ui(self):
        # Title Text
        if self.titletext is not None:
            self.title = Label(
                font_size=18,
                bold=True,
                anchor_x='center',
                anchor_y='center',
                label=self.titletext)
            self.title.x = self.width/2 + self.x
            self.title.y = self.height - 20 + self.y

        # Delete Button
        if self.deletable:
            self.db = MTToggleButton(
                label='X',
                pos=(self.x + self.width - 80, self.y + self.height - 40),
                size=(80, 40),
                cls='kineticlist-delete')
            self.db.push_handlers(on_press=self.toggle_delete)
            self.widgets.append(self.db)

        # Search Button and Input Text Area
        if self.searchable:
            self.sb = MTToggleButton(
                label='S',  #Button
                pos=(self.x, self.y + self.width - 40),
                size=(80, 40),
                cls='kineticlist-search')

            self.sb.push_handlers(on_press=self.toggle_search)
            self.sb.parent = self
            self.widgets.append(self.sb)

            self.sinput = pymt.MTTextInput(pos=
                (self.x, self.y + self.height - 40), size=(80, 40),
                style={'font-size': 20})
            self.sinput.parent = self
            self.sinput.push_handlers(on_text_change=self.apply_filter)
            self.widgets.insert(0, self.sinput)

            # Animations to hide and show the search text input box
            self._a_sinput_in = Animation(y=self.y + self.height - 40 -
                                         self.sinput.size[1], duration=0.5,
                                         f='ease_out_cubic')
            self._a_sinput_out = Animation(y=self.y + self.height -
                                          self.sinput.size[1], duration=0.5,
                                         f='ease_out_cubic')

    def on_delete(self, child):
        pass

    def clear(self):
        self.children = SafeList()
        self.pchildren = SafeList()
        self.xoffset = self.yoffset = 0

    def add_widget(self, widget, **kwargs):
        super(MTKineticList, self).add_widget(widget, **kwargs)
        self.pchildren.append(widget)

    def remove_widget(self, widget):
        super(MTKineticList, self).remove_widget(widget)
        if widget in self.pchildren:
            self.pchildren.remove(widget)
        self.dispatch_event('on_delete', widget)

    def toggle_delete(self, touch):
        '''Toggles the delete buttons on items
        Attached to the on_press handler of the delete button(self.db)
        '''
        if self.db.state == 'down':
            for child in self.children[:]:
                child.show_delete()
        else:
            for child in self.children[:]:
                child.hide_delete()

    def toggle_search(self, touch):
        '''Toggles the search area
        Attached to the on_press handler of self.sb(the green search button)
        '''
        if self.sb.state == 'down':
            self._a_sinput_in.animate(self.sinput)
        else:
            try:
                self.sinput.hide_keyboard()
            except:
                # There isn't a keyboard, so it throws a ValueError
                pass
            self.sinput.label = ''
            self._a_sinput_out.animate(self.sinput)

    def apply_filter(self, text):
        '''Applies the filter to the current children set'''
        self.search(text, 'label')
        #Move them so you don't have to scroll up to see them
        self.yoffset = self.padding_y
        self.xoffset = self.padding_x

    def filter(self, pattern, attr):
        '''Given an attribute of the children, and a pattern, return
        a list of the children with which pattern is in attr
        '''
        return filter(lambda c: pattern in str(getattr(c, attr)), self.pchildren)

    def search(self, pattern, attr):
        '''Apply a search pattern to the current set of children'''
        result = self.filter(pattern, attr)
        self.children.clear()
        for item in result:
            self.children.append(item)

    def endsearch(self):
        '''Resets the children set to the full set'''
        self.children.clear()
        for item in self.pchildren:
            self.children.append(item)

    def _get_total_width(self, items, axis):
        '''Given a list of items and an axis, return the space
        they take up(in pixels)
        '''
        total = 0
        if axis == 'width':
            for item in items:
                total += (item.width + self.padding_x)
        elif axis == 'height':
            for item in items:
                total += (item.height + self.padding_y)

        return total

    def goto_head(self):
        if not self.h_limit:
            self.yoffset = -self._get_total_width(self.children, 'height')/self.w_limit + self.size[1] - 100
        else:
            self.xoffset = self._get_total_width(self.children, 'width')/self.h_limit + self.size[0] - 100

    def do_layout(self):
        '''Apply layout to all the items'''

        t = index = 0

        # adapt value for direction
        w2          = self.width / 2.
        h2          = self.height / 2.
        inverse     = 0
        limit       = self.w_limit
        width_attr  = 'width'
        height_attr = 'height'
        xoffset     = self.xoffset
        sx          = self.x
        y           = self.y + self.yoffset
        padding_x   = self.padding_x
        padding_y   = self.padding_y

        # inverse
        if not self.w_limit:
            inverse     = 1
            limit       = self.h_limit
            width_attr  = 'height'
            height_attr = 'width'
            xoffset     = self.yoffset
            y           = self.x + self.xoffset
            padding_x   = self.padding_y
            padding_y   = self.padding_x
            w2, h2      = h2, w2
            sx          = self.y

        # calculate size of actual content
        size = 0
        for i in xrange(len(self.children)):
            if i % limit == 0:
                maxrange = min(i + limit, len(self.children))
                childrens = [self.children[z] for z in range(i, maxrange)]
                h = max((c.__getattribute__(height_attr) for c in childrens))
                size += h + padding_y
        self._last_content_size = size

        # add little padding for good looking.
        y += padding_y
        ny = y

        # recalculate position for each children
        for child in self.children[:]:

            # each row, calculate the height, advance y and reset x
            if index % limit == 0:

                # set y axis to the previous calculated position
                y = ny

                # get children in the row
                maxrange = min(t + limit, len(self.children))
                childrens = [self.children[z] for z in range(t, maxrange)]

                # take the largest height in the current row
                if len(childrens):
                    h = max((c.__getattribute__(height_attr) for c in childrens))
                else:
                    h = 0

                # prepare the y axis for next loop
                ny = y + h + padding_y

                # reset x for this row.
                if self.align == 'center':
                    x = sx + w2 + xoffset - \
                        (self._get_total_width(childrens, width_attr) / 2.)
                elif self.align == 'left':
                    x = 0
                elif self.align == 'right':
                    x = getattr(self, width_attr) - getattr(child, width_attr) - xoffset
                t += limit

            # reposition x
            if not inverse:
                child.kx = x + padding_x
                child.ky = y
            else:
                child.ky = x + padding_x
                child.kx = y
            x += child.__getattribute__(width_attr) + padding_x

            # Increment index
            index += 1

    def on_touch_down(self, touch):
        if not self.collide_point(touch.x, touch.y):
            return

        # ok, it's a touch for us. grab it !
        touch.grab(self)

        # first, check if own widget take the touch
        for w in reversed(self.widgets[:]):
            if w.on_touch_down(touch):
                return True

        # initiate kinetic movement.
        self.vx = self.vy = 0
        self.touch[touch.id] = {
            'ox': touch.x,
            'oy': touch.y,
            'xmot': 0,
            'ymot': 0,
            'travelx' : 0, #How far the blob has traveled total in the x axis
            'travely' : 0, #^
        }
        return True

    def on_touch_move(self, touch):
        # accept only grabbed touch by us
        if touch.grab_current != self:
            return

        # ok, if it's not a kinetic movement,
        # dispatch to children
        if touch.id not in self.touch:
            for w in reversed(self.widgets[:]):
                if w.on_touch_move(touch):
                    return True
            return

        # it's a kinetic movement, process it.
        t = self.touch[touch.id]
        t['xmot'] = touch.x - t['ox']
        t['ymot'] = touch.y - t['oy']
        t['ox'] = touch.x
        t['oy'] = touch.y
        t['travelx'] += abs(t['xmot'])
        t['travely'] += abs(t['ymot'])
        self.xoffset += t['xmot'] * self.do_x
        self.yoffset += t['ymot'] * self.do_y
        self.ensure_bounding()

    def on_touch_up(self, touch):
        # accept only grabbed touch by us
        if touch.grab_current != self:
            return

        # it's an up, ungrab us !
        touch.ungrab(self)

        if touch.id not in self.touch:
            for w in reversed(self.widgets[:]):
                if w.on_touch_up(touch):
                    return True
            return

        t = self.touch[touch.id]
        self.vx = t['xmot']
        self.vy = t['ymot']

        # check if we can transmit event to children
        if (self.do_x and t['travelx'] > self.trigger_distance) or \
           (self.do_y and t['travely'] > self.trigger_distance):
            return True

        # ok, the trigger distance is enough, we can dispatch event.
        # will not work if children grab the touch in down state :/
        for child in reversed(self.children[:]):
            must_break = child.dispatch_event('on_touch_down', touch)
            old_grab_current = touch.grab_current
            touch.grab_current = child
            child.dispatch_event('on_touch_up', touch)
            touch.grab_current = old_grab_current
            if must_break:
                break
        return True

    def ensure_bounding(self):
        size = float(self._last_content_size)
        if size <= 0:
            return

        self._scrollbar_size = 1
        self._scrollbar_index = 0

        if self.do_y:
            if size < self.height:
                self.yoffset = 0
            else:
                self.yoffset = boundary(self.yoffset, -size + self.height, 0)
                self._scrollbar_size = self.height / size
                self._scrollbar_index = -self.yoffset / size
        if self.do_x:
            if size < self.width:
                self.xoffset = 0
            else:
                self.xoffset = boundary(self.xoffset, -size + self.width, 0)
                self._scrollbar_size = self.width / size
                self._scrollbar_index = -self.xoffset / size

    def process_kinetic(self):
        '''Apply kinetic movement to all the items'''
        dt = getFrameDt()
        self.vx /= 1 + (self.friction * dt)
        self.vy /= 1 + (self.friction * dt)

        self.xoffset += self.vx * self.do_x
        self.yoffset += self.vy * self.do_y

        self.ensure_bounding()

    def draw(self):
        # background
        set_color(*self.style.get('bg-color'))
        drawCSSRectangle(pos=self.pos, size=self.size, style=self.style)

        # draw children
        self.stencil_push()
        for w in self.children[:]:
            # internal update of children
            w.update()
            # optimization to draw only viewed children
            if self.do_y and (w.y + w.height < self.y or w.y > self.y + self.height):
                continue
            if self.do_x and (w.x + w.width < self.x or w.x > self.x + self.width):
                continue
            w.on_draw()
        self.stencil_pop()

        # draw widgets
        for w in self.widgets:
            w.dispatch_event('on_draw')
        # title bar
        if self.titletext is not None:
            set_color(*self.style.get('title-color'))
            w = 0
            if self.searchable:
                x = 80
                w += 80
            else:
                x = 0
            if self.deletable:
                w += 80
            drawCSSRectangle(pos=(self.x + x, self.height + self.y - 40),
                             size=(self.width - w, 40), prefix='title',
                             style=self.style)
            self.title.x = self.width/2 + self.x
            self.title.y = self.height - 20 + self.y
            self.title.draw()


        # scrollbar
        sb_size = self.style.get('scrollbar-size')
        if sb_size > 0:
            mtop, mright, mbottom, mleft = self.style.get('scrollbar-margin')
            if self.do_y:
                pos = [self.x + self.width - mright - sb_size, self.y + mbottom]
                size = [sb_size, self.height - mbottom - mtop]
                pos[1] += size[1] * self._scrollbar_index
                size[1] = size[1] * self._scrollbar_size
            if self.do_x:
                pos = [self.x + mleft, self.y + self.height - mtop - sb_size]
                size = [self.width - mleft - mright, sb_size]
                pos[0] += size[0] * self._scrollbar_index
                size[0] = size[0] * self._scrollbar_size
            set_color(*self.style.get('scrollbar-color'))
            drawRectangle(pos=pos, size=size)

    def on_draw(self):
        if not self.visible:
            return
        self.do_layout()
        self.process_kinetic()
        self.draw()
Пример #7
0
    def __init__(self, **kwargs):
        kwargs.setdefault('padding_x', 4)
        kwargs.setdefault('padding_y', 4)
        kwargs.setdefault('w_limit', 1)
        kwargs.setdefault('h_limit', 0)
        kwargs.setdefault('do_x', False)
        kwargs.setdefault('do_y', True)
        kwargs.setdefault('title', 'No title')
        kwargs.setdefault('deletable', True)
        kwargs.setdefault('searchable', True)
        kwargs.setdefault('align', 'center')

        super(MTKineticList, self).__init__(**kwargs)

        self.register_event_type('on_delete')

        self._a_sinput_out = None
        self._a_sinput_in = None
        self.title = Label('')
        self.sb = None
        self.sinput = None

        self.do_x = kwargs.get('do_x')
        self.do_y = kwargs.get('do_y')
        self.titletext = kwargs.get('title')
        self.deletable = kwargs.get('deletable')
        self.searchable = kwargs.get('searchable')
        self.padding_x = kwargs.get('padding_x')
        self.padding_y = kwargs.get('padding_y')
        self.w_limit = kwargs.get('w_limit')
        self.h_limit = kwargs.get('h_limit')
        self.align = kwargs.get('align')
        self.trigger_distance = kwargs.get(
            'trigger_distance',
            pymt_config.getint('widgets', 'list_trigger_distance'))
        self.friction = kwargs.get(
            'friction', pymt_config.getint('widgets', 'list_friction'))

        if self.w_limit and self.h_limit:
            raise Exception('You cannot limit both axes')
        elif not (self.w_limit or self.h_limit):
            raise Exception('You must limit at least one axis')

        # How far to offset tself.deletable and he axes(used for scrolling/panning)
        self.xoffset = 0
        self.yoffset = 0
        # X and Y translation vectors for the kinetic movement
        self.vx = 0
        self.vy = 0
        # List of all children, whatever will be the search
        self.pchildren = []
        # For extra blob stats
        self.touch = {}
        # Holds widgets not a part of the scrolling(search button, etc)
        self.widgets = []
        self._last_content_size = 0
        self._scrollbar_index = 0
        self._scrollbar_size = 0

        # create the UI part.
        self._create_ui()
Пример #8
0
class MTKineticList(MTStencilContainer):
    '''This is a kinetic container widget, that allows you to make
    a kinetic list scrolling in either direction.

    Some parameters are customizable in global configuration ::

        [widgets]
        list_friction = 10
        list_trigger_distance = 5

    :Parameters:
        `align` : string, default to 'center'
            Alignement of widget inside the row (or col). Can be
            one of 'center', 'left', 'right'
        `friction` : float, defaults to 10
            The Pseudo-friction of the pseudo-kinetic scrolling.
            Formula for friction is ::

                acceleration = 1 + friction * frame_delta_time

        `padding_x` : int, defaults to 4
            The spacing between scrolling items on the x axis
        `padding_y` : int, defaults to 4
            The spacing between scrolling items on the y axis
        `w_limit` : int, defaults to 1
            The limit of items that will appear horizontally.
            When this is set to a non-zero value the width(in
            terms of items in the kinetic list) will be w_limit,
            and the height will continually expand.
        `h_limit` : int, defaults to 0
            Exect opposite of w_limit.  If I didn't make either
            this or w_limit clear go bug xelapond
        `do_x` : bool, defaults to False
            Enable scrolling on the X axis
        `do_y` : bool, defaults to True
            Enable scrolling on the Y axis
        `title` : string, defaults to <Title Goes Here>
            Sets the title of the widget, which appears in 20
            point font at the top
        `deletable` : bool, defaults to True
            When enabled it allows you to delete children by
            entering delete mode(red button in upper left)
        `searchable` : bool, defaults to True
            When enabled it allows you to enter search mode
            and filter items
        `trigger_distance` : int, default to 3
            Maximum trigger distance to dispatch event on children
            (this mean if you move too much, trigger will not happen.)

    :Styles:
        `bg-color` : color
             Background color of the widget
        `scrollbar-size` : int
            Size of scrollbar in pixel (use 0 to disable it.)
        `scrollbar-color` : color
            Color of scrollbar
        `scrollbar-margin` : int int int int
            Margin top/right/bottom/left of scrollbar (left are not used.)

    :Events:
        `on_delete` (child)
            Fired when an item gets deleted.
    '''
    def __init__(self, **kwargs):
        kwargs.setdefault('padding_x', 4)
        kwargs.setdefault('padding_y', 4)
        kwargs.setdefault('w_limit', 1)
        kwargs.setdefault('h_limit', 0)
        kwargs.setdefault('do_x', False)
        kwargs.setdefault('do_y', True)
        kwargs.setdefault('title', 'No title')
        kwargs.setdefault('deletable', True)
        kwargs.setdefault('searchable', True)
        kwargs.setdefault('align', 'center')

        super(MTKineticList, self).__init__(**kwargs)

        self.register_event_type('on_delete')

        self._a_sinput_out = None
        self._a_sinput_in = None
        self.title = Label('')
        self.sb = None
        self.sinput = None

        self.do_x = kwargs.get('do_x')
        self.do_y = kwargs.get('do_y')
        self.titletext = kwargs.get('title')
        self.deletable = kwargs.get('deletable')
        self.searchable = kwargs.get('searchable')
        self.padding_x = kwargs.get('padding_x')
        self.padding_y = kwargs.get('padding_y')
        self.w_limit = kwargs.get('w_limit')
        self.h_limit = kwargs.get('h_limit')
        self.align = kwargs.get('align')
        self.trigger_distance = kwargs.get(
            'trigger_distance',
            pymt_config.getint('widgets', 'list_trigger_distance'))
        self.friction = kwargs.get(
            'friction', pymt_config.getint('widgets', 'list_friction'))

        if self.w_limit and self.h_limit:
            raise Exception('You cannot limit both axes')
        elif not (self.w_limit or self.h_limit):
            raise Exception('You must limit at least one axis')

        # How far to offset tself.deletable and he axes(used for scrolling/panning)
        self.xoffset = 0
        self.yoffset = 0
        # X and Y translation vectors for the kinetic movement
        self.vx = 0
        self.vy = 0
        # List of all children, whatever will be the search
        self.pchildren = []
        # For extra blob stats
        self.touch = {}
        # Holds widgets not a part of the scrolling(search button, etc)
        self.widgets = []
        self._last_content_size = 0
        self._scrollbar_index = 0
        self._scrollbar_size = 0

        # create the UI part.
        self._create_ui()

    def _create_ui(self):
        # Title Text
        if self.titletext is not None:
            self.title = Label(font_size=18,
                               bold=True,
                               anchor_x='center',
                               anchor_y='center',
                               label=self.titletext)
            self.title.x = self.width / 2 + self.x
            self.title.y = self.height - 20 + self.y

        # Delete Button
        if self.deletable:
            self.db = MTToggleButton(label='X',
                                     pos=(self.x + self.width - 80,
                                          self.y + self.height - 40),
                                     size=(80, 40),
                                     cls='kineticlist-delete')
            self.db.push_handlers(on_press=self.toggle_delete)
            self.widgets.append(self.db)

        # Search Button and Input Text Area
        if self.searchable:
            self.sb = MTToggleButton(
                label='S',  #Button
                pos=(self.x, self.y + self.width - 40),
                size=(80, 40),
                cls='kineticlist-search')

            self.sb.push_handlers(on_press=self.toggle_search)
            self.sb.parent = self
            self.widgets.append(self.sb)

            self.sinput = pymt.MTTextInput(pos=(self.x,
                                                self.y + self.height - 40),
                                           size=(80, 40),
                                           style={'font-size': 20})
            self.sinput.parent = self
            self.sinput.push_handlers(on_text_change=self.apply_filter)
            self.widgets.insert(0, self.sinput)

            # Animations to hide and show the search text input box
            self._a_sinput_in = Animation(y=self.y + self.height - 40 -
                                          self.sinput.size[1],
                                          duration=0.5,
                                          f='ease_out_cubic')
            self._a_sinput_out = Animation(y=self.y + self.height -
                                           self.sinput.size[1],
                                           duration=0.5,
                                           f='ease_out_cubic')

    def on_delete(self, child):
        pass

    def clear(self):
        self.children = SafeList()
        self.pchildren = SafeList()
        self.xoffset = self.yoffset = 0

    def add_widget(self, widget, **kwargs):
        super(MTKineticList, self).add_widget(widget, **kwargs)
        self.pchildren.append(widget)

    def remove_widget(self, widget):
        super(MTKineticList, self).remove_widget(widget)
        if widget in self.pchildren:
            self.pchildren.remove(widget)
        self.dispatch_event('on_delete', widget)

    def toggle_delete(self, touch):
        '''Toggles the delete buttons on items
        Attached to the on_press handler of the delete button(self.db)
        '''
        if self.db.state == 'down':
            for child in self.children[:]:
                child.show_delete()
        else:
            for child in self.children[:]:
                child.hide_delete()

    def toggle_search(self, touch):
        '''Toggles the search area
        Attached to the on_press handler of self.sb(the green search button)
        '''
        if self.sb.state == 'down':
            self._a_sinput_in.animate(self.sinput)
        else:
            try:
                self.sinput.hide_keyboard()
            except:
                # There isn't a keyboard, so it throws a ValueError
                pass
            self.sinput.label = ''
            self._a_sinput_out.animate(self.sinput)

    def apply_filter(self, text):
        '''Applies the filter to the current children set'''
        self.search(text, 'label')
        #Move them so you don't have to scroll up to see them
        self.yoffset = self.padding_y
        self.xoffset = self.padding_x

    def filter(self, pattern, attr):
        '''Given an attribute of the children, and a pattern, return
        a list of the children with which pattern is in attr
        '''
        return filter(lambda c: pattern in str(getattr(c, attr)),
                      self.pchildren)

    def search(self, pattern, attr):
        '''Apply a search pattern to the current set of children'''
        result = self.filter(pattern, attr)
        self.children.clear()
        for item in result:
            self.children.append(item)

    def endsearch(self):
        '''Resets the children set to the full set'''
        self.children.clear()
        for item in self.pchildren:
            self.children.append(item)

    def _get_total_width(self, items, axis):
        '''Given a list of items and an axis, return the space
        they take up(in pixels)
        '''
        total = 0
        if axis == 'width':
            for item in items:
                total += (item.width + self.padding_x)
        elif axis == 'height':
            for item in items:
                total += (item.height + self.padding_y)

        return total

    def goto_head(self):
        if not self.h_limit:
            self.yoffset = -self._get_total_width(
                self.children, 'height') / self.w_limit + self.size[1] - 100
        else:
            self.xoffset = self._get_total_width(
                self.children, 'width') / self.h_limit + self.size[0] - 100

    def do_layout(self):
        '''Apply layout to all the items'''

        t = index = 0

        # adapt value for direction
        w2 = self.width / 2.
        h2 = self.height / 2.
        inverse = 0
        limit = self.w_limit
        width_attr = 'width'
        height_attr = 'height'
        xoffset = self.xoffset
        sx = self.x
        y = self.y + self.yoffset
        padding_x = self.padding_x
        padding_y = self.padding_y

        # inverse
        if not self.w_limit:
            inverse = 1
            limit = self.h_limit
            width_attr = 'height'
            height_attr = 'width'
            xoffset = self.yoffset
            y = self.x + self.xoffset
            padding_x = self.padding_y
            padding_y = self.padding_x
            w2, h2 = h2, w2
            sx = self.y

        # calculate size of actual content
        size = 0
        for i in xrange(len(self.children)):
            if i % limit == 0:
                maxrange = min(i + limit, len(self.children))
                childrens = [self.children[z] for z in range(i, maxrange)]
                h = max((c.__getattribute__(height_attr) for c in childrens))
                size += h + padding_y
        self._last_content_size = size

        # add little padding for good looking.
        y += padding_y
        ny = y

        # recalculate position for each children
        for child in self.children[:]:

            # each row, calculate the height, advance y and reset x
            if index % limit == 0:

                # set y axis to the previous calculated position
                y = ny

                # get children in the row
                maxrange = min(t + limit, len(self.children))
                childrens = [self.children[z] for z in range(t, maxrange)]

                # take the largest height in the current row
                if len(childrens):
                    h = max(
                        (c.__getattribute__(height_attr) for c in childrens))
                else:
                    h = 0

                # prepare the y axis for next loop
                ny = y + h + padding_y

                # reset x for this row.
                if self.align == 'center':
                    x = sx + w2 + xoffset - \
                        (self._get_total_width(childrens, width_attr) / 2.)
                elif self.align == 'left':
                    x = 0
                elif self.align == 'right':
                    x = getattr(self, width_attr) - getattr(
                        child, width_attr) - xoffset
                t += limit

            # reposition x
            if not inverse:
                child.kx = x + padding_x
                child.ky = y
            else:
                child.ky = x + padding_x
                child.kx = y
            x += child.__getattribute__(width_attr) + padding_x

            # Increment index
            index += 1

    def on_touch_down(self, touch):
        if not self.collide_point(touch.x, touch.y):
            return

        # ok, it's a touch for us. grab it !
        touch.grab(self)

        # first, check if own widget take the touch
        for w in reversed(self.widgets[:]):
            if w.on_touch_down(touch):
                return True

        # initiate kinetic movement.
        self.vx = self.vy = 0
        self.touch[touch.id] = {
            'ox': touch.x,
            'oy': touch.y,
            'xmot': 0,
            'ymot': 0,
            'travelx': 0,  #How far the blob has traveled total in the x axis
            'travely': 0,  #^
        }
        return True

    def on_touch_move(self, touch):
        # accept only grabbed touch by us
        if touch.grab_current != self:
            return

        # ok, if it's not a kinetic movement,
        # dispatch to children
        if touch.id not in self.touch:
            for w in reversed(self.widgets[:]):
                if w.on_touch_move(touch):
                    return True
            return

        # it's a kinetic movement, process it.
        t = self.touch[touch.id]
        t['xmot'] = touch.x - t['ox']
        t['ymot'] = touch.y - t['oy']
        t['ox'] = touch.x
        t['oy'] = touch.y
        t['travelx'] += abs(t['xmot'])
        t['travely'] += abs(t['ymot'])
        self.xoffset += t['xmot'] * self.do_x
        self.yoffset += t['ymot'] * self.do_y
        self.ensure_bounding()

    def on_touch_up(self, touch):
        # accept only grabbed touch by us
        if touch.grab_current != self:
            return

        # it's an up, ungrab us !
        touch.ungrab(self)

        if touch.id not in self.touch:
            for w in reversed(self.widgets[:]):
                if w.on_touch_up(touch):
                    return True
            return

        t = self.touch[touch.id]
        self.vx = t['xmot']
        self.vy = t['ymot']

        # check if we can transmit event to children
        if (self.do_x and t['travelx'] > self.trigger_distance) or \
           (self.do_y and t['travely'] > self.trigger_distance):
            return True

        # ok, the trigger distance is enough, we can dispatch event.
        # will not work if children grab the touch in down state :/
        for child in reversed(self.children[:]):
            must_break = child.dispatch_event('on_touch_down', touch)
            old_grab_current = touch.grab_current
            touch.grab_current = child
            child.dispatch_event('on_touch_up', touch)
            touch.grab_current = old_grab_current
            if must_break:
                break
        return True

    def ensure_bounding(self):
        size = float(self._last_content_size)
        if size <= 0:
            return

        self._scrollbar_size = 1
        self._scrollbar_index = 0

        if self.do_y:
            if size < self.height:
                self.yoffset = 0
            else:
                self.yoffset = boundary(self.yoffset, -size + self.height, 0)
                self._scrollbar_size = self.height / size
                self._scrollbar_index = -self.yoffset / size
        elif self.do_x:
            if size < self.width:
                self.xoffset = 0
            else:
                self.xoffset = boundary(self.xoffset, -size + self.width, 0)
                self._scrollbar_size = self.width / size
                self._scrollbar_index = -self.xoffset / size

    def process_kinetic(self):
        '''Apply kinetic movement to all the items'''
        dt = getFrameDt()
        self.vx /= 1 + (self.friction * dt)
        self.vy /= 1 + (self.friction * dt)

        self.xoffset += self.vx * self.do_x
        self.yoffset += self.vy * self.do_y

        self.ensure_bounding()

    def draw(self):
        # background
        set_color(*self.style.get('bg-color'))
        drawCSSRectangle(pos=self.pos, size=self.size, style=self.style)

        # draw children
        self.stencil_push()
        for w in self.children[:]:
            # internal update of children
            w.update()
            # optimization to draw only viewed children
            if self.do_y and (w.y + w.height < self.y
                              or w.y > self.y + self.height):
                continue
            if self.do_x and (w.x + w.width < self.x
                              or w.x > self.x + self.width):
                continue
            w.on_draw()
        self.stencil_pop()

        # draw widgets
        for w in self.widgets:
            w.dispatch_event('on_draw')
        # title bar
        if self.titletext is not None:
            set_color(*self.style.get('title-color'))
            w = 0
            if self.searchable:
                x = 80
                w += 80
            else:
                x = 0
            if self.deletable:
                w += 80
            drawCSSRectangle(pos=(self.x + x, self.height + self.y - 40),
                             size=(self.width - w, 40),
                             prefix='title',
                             style=self.style)
            self.title.x = self.width / 2 + self.x
            self.title.y = self.height - 20 + self.y
            self.title.draw()

        # scrollbar
        sb_size = self.style.get('scrollbar-size')
        if sb_size > 0:
            mtop, mright, mbottom, mleft = self.style.get('scrollbar-margin')
            if self.do_y:
                pos = [
                    self.x + self.width - mright - sb_size, self.y + mbottom
                ]
                size = [sb_size, self.height - mbottom - mtop]
                pos[1] += size[1] * self._scrollbar_index
                size[1] = size[1] * self._scrollbar_size
            elif self.do_x:
                pos = [self.x + mleft, self.y + self.height - mtop - sb_size]
                size = [self.width - mleft - mright, sb_size]
                pos[0] += size[0] * self._scrollbar_index
                size[0] = size[0] * self._scrollbar_size
            set_color(*self.style.get('scrollbar-color'))
            drawRectangle(pos=pos, size=size)

    def on_draw(self):
        if not self.visible:
            return
        self.do_layout()
        self.process_kinetic()
        self.draw()
Пример #9
0
Файл: label.py Проект: imc/pymt
class MTFormLabel(MTAbstractFormWidget):
    '''Form label : a simple text label with alignment support

    :Parameters:
        `label` : str, default is ''
            Text of label
        `multiline` : bool, default is False
            Indicate if label is multiline. You should indicate a width :)
        `halign` : str, default is 'center'
            Horizontal alignement, can be 'left', 'center', 'right'
        `valign` : str, default is 'center'
            Vertical alignement, can be 'top', 'center', 'bottom'

    :Styles:
        `font-name` : str
            Font name
        `font-size` : int
            Font size
        `font-weight` : str
            Style of font, can be 'bold', 'italic', 'bolditalic'
        `color` : list
            Font color
    '''

    def __init__(self, **kwargs):
        kwargs.setdefault('label', '')
        kwargs.setdefault('halign', 'center')
        kwargs.setdefault('valign', 'center')
        kwargs.setdefault('multiline', False)
        super(MTFormLabel, self).__init__(**kwargs)
        self.multiline  = kwargs.get('multiline')
        self.label      = kwargs.get('label')
        self.halign     = kwargs.get('halign')
        self.valign     = kwargs.get('valign')

    def _get_label(self):
        return self._label
    def _set_label(self, label):
        self._label = label
        opts = {}
        opts['anchor_y'] = 'bottom'
        if self.style.get('font-name') != '':
            opts['font_name'] = self.style.get('font-name')
        if int(self.style.get('font-size')) > 0:
            opts['font_size'] = int(self.style.get('font-size'))
        if self.style.get('font-weight'):
            if self.style.get('font-weight') in ['italic', 'bolditalic']:
                opts['italic'] = True
            if self.style.get('font-weight') in ['bold', 'bolditalic']:
                opts['bold'] = True
        opts['color'] = map(lambda x: int(x * 255), self.style.get('color'))
        opts['text'] = label
        if self.multiline:
            opts['multiline'] = self.multiline
            opts['width'] = self.width

        self._label_obj = Label(label=label, **opts)
        self.size = self._label_obj.content_width, self._label_obj.content_height
    label = property(_get_label, _set_label)

    def get_content_pos(self, content_width, content_height):
        x, y = self.pos
        if self.halign == 'left':
            pass
        elif self.halign == 'center':
            x = x + (self.width - content_width) / 2
        elif self.halign == 'right':
            x = x + self.width - content_width
        if self.valign == 'top':
            y = y + self.height - content_height
        elif self.valign == 'center':
            y = y + (self.height - content_height) / 2
        elif self.valign == 'bottom':
            pass
        return (x, y)

    def draw(self):
        self._label_obj.x, self._label_obj.y = self.get_content_pos(
            self._label_obj.content_width, self._label_obj.content_height)
        self._label_obj.draw()