Beispiel #1
0
class AKFloatingRoundedAppbarAvatarItem(AKFloatingRoundedAppbarItemBase):
    source = StringProperty()
    text = StringProperty()
    text_color = ListProperty()
Beispiel #2
0
class MapView(Widget):
    """MapView is the widget that control the map displaying, navigation, and
    layers management.
    """

    lon = NumericProperty()
    """Longitude at the center of the widget
    """

    lat = NumericProperty()
    """Latitude at the center of the widget
    """

    zoom = NumericProperty(0)
    """Zoom of the widget. Must be between :meth:`MapSource.get_min_zoom` and
    :meth:`MapSource.get_max_zoom`. Default to 0.
    """

    map_source = ObjectProperty(MapSource())
    """Provider of the map, default to a empty :class:`MapSource`.
    """

    double_tap_zoom = BooleanProperty(False)
    """If True, this will activate the double-tap to zoom.
    """

    pause_on_action = BooleanProperty(True)
    """Pause any map loading / tiles loading when an action is done.
    This allow better performance on mobile, but can be safely deactivated on
    desktop.
    """

    snap_to_zoom = BooleanProperty(True)
    """When the user initiate a zoom, it will snap to the closest zoom for
    better graphics. The map can be blur if the map is scaled between 2 zoom.
    Default to True, even if it doesn't fully working yet.
    """

    animation_duration = NumericProperty(100)
    """Duration to animate Tiles alpha from 0 to 1 when it's ready to show.
    Default to 100 as 100ms. Use 0 to deactivate.
    """

    delta_x = NumericProperty(0)
    delta_y = NumericProperty(0)
    background_color = ListProperty([181 / 255., 208 / 255., 208 / 255., 1])
    cache_dir = StringProperty(CACHE_DIR)
    _zoom = NumericProperty(0)
    _pause = BooleanProperty(False)
    _scale = 1.
    _disabled_count = 0

    __events__ = ["on_map_relocated"]

    # Public API

    @property
    def viewport_pos(self):
        vx, vy = self._scatter.to_local(self.x, self.y)
        return vx - self.delta_x, vy - self.delta_y

    @property
    def scale(self):
        if self._invalid_scale:
            self._invalid_scale = False
            self._scale = self._scatter.scale
        return self._scale

    def get_bbox(self, margin=0):
        """Returns the bounding box from the bottom/left (lat1, lon1) to
        top/right (lat2, lon2).
        """
        x1, y1 = self.to_local(0 - margin, 0 - margin)
        x2, y2 = self.to_local((self.width + margin), (self.height + margin))
        c1 = self.get_latlon_at(x1, y1)
        c2 = self.get_latlon_at(x2, y2)
        return Bbox((c1.lat, c1.lon, c2.lat, c2.lon))

    bbox = AliasProperty(get_bbox, None, bind=["lat", "lon", "_zoom"])

    def unload(self):
        """Unload the view and all the layers.
        It also cancel all the remaining downloads.
        """
        self.remove_all_tiles()

    def get_window_xy_from(self, lat, lon, zoom):
        """Returns the x/y position in the widget absolute coordinates
        from a lat/lon"""
        scale = self.scale
        vx, vy = self.viewport_pos
        ms = self.map_source
        x = ms.get_x(zoom, lon) - vx
        y = ms.get_y(zoom, lat) - vy
        x *= scale
        y *= scale
        x = x + self.pos[0]
        y = y + self.pos[1]
        return x, y

    def center_on(self, *args):
        """Center the map on the coordinate :class:`Coordinate`, or a (lat, lon)
        """
        map_source = self.map_source
        zoom = self._zoom

        if len(args) == 1 and isinstance(args[0], Coordinate):
            coord = args[0]
            lat = coord.lat
            lon = coord.lon
        elif len(args) == 2:
            lat, lon = args
        else:
            raise Exception("Invalid argument for center_on")
        lon = clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE)
        lat = clamp(lat, MIN_LATITUDE, MAX_LATITUDE)
        scale = self._scatter.scale
        x = map_source.get_x(zoom, lon) - self.center_x / scale
        y = map_source.get_y(zoom, lat) - self.center_y / scale
        self.delta_x = -x
        self.delta_y = -y
        self.lon = lon
        self.lat = lat
        self._scatter.pos = 0, 0
        self.trigger_update(True)

    def set_zoom_at(self, zoom, x, y, scale=None):
        """Sets the zoom level, leaving the (x, y) at the exact same point
        in the view.
        """
        zoom = clamp(zoom, self.map_source.get_min_zoom(),
                     self.map_source.get_max_zoom())
        if int(zoom) == int(self._zoom):
            if scale is None:
                return
            elif scale == self.scale:
                return
        scale = scale or 1.

        # first, rescale the scatter
        scatter = self._scatter
        scale = clamp(scale, scatter.scale_min, scatter.scale_max)
        rescale = scale * 1.0 / scatter.scale
        scatter.apply_transform(Matrix().scale(rescale, rescale, rescale),
                                post_multiply=True,
                                anchor=scatter.to_local(x, y))

        # adjust position if the zoom changed
        c1 = self.map_source.get_col_count(self._zoom)
        c2 = self.map_source.get_col_count(zoom)
        if c1 != c2:
            f = float(c2) / float(c1)
            self.delta_x = scatter.x + self.delta_x * f
            self.delta_y = scatter.y + self.delta_y * f
            # back to 0 every time
            scatter.apply_transform(Matrix().translate(-scatter.x, -scatter.y,
                                                       0),
                                    post_multiply=True)

        # avoid triggering zoom changes.
        self._zoom = zoom
        self.zoom = self._zoom

    def on_zoom(self, instance, zoom):
        if zoom == self._zoom:
            return
        x = self.map_source.get_x(zoom, self.lon) - self.delta_x
        y = self.map_source.get_y(zoom, self.lat) - self.delta_y
        self.set_zoom_at(zoom, x, y)
        self.center_on(self.lat, self.lon)

    def get_latlon_at(self, x, y, zoom=None):
        """Return the current :class:`Coordinate` within the (x, y) widget
        coordinate.
        """
        if zoom is None:
            zoom = self._zoom
        vx, vy = self.viewport_pos
        scale = self._scale
        return Coordinate(lat=self.map_source.get_lat(zoom, y / scale + vy),
                          lon=self.map_source.get_lon(zoom, x / scale + vx))

    def add_marker(self, marker, layer=None):
        """Add a marker into the layer. If layer is None, it will be added in
        the default marker layer. If there is no default marker layer, a new
        one will be automatically created
        """
        if layer is None:
            if not self._default_marker_layer:
                layer = MarkerMapLayer()
                self.add_layer(layer)
            else:
                layer = self._default_marker_layer
        layer.add_widget(marker)
        layer.set_marker_position(self, marker)

    def remove_marker(self, marker):
        """Remove a marker from its layer
        """
        marker.detach()

    def add_layer(self, layer, mode="window"):
        """Add a new layer to update at the same time the base tile layer.
        mode can be either "scatter" or "window". If "scatter", it means the
        layer will be within the scatter transformation. It's perfect if you
        want to display path / shape, but not for text.
        If "window", it will have no transformation. You need to position the
        widget yourself: think as Z-sprite / billboard.
        Defaults to "window".
        """
        assert (mode in ("scatter", "window"))
        if self._default_marker_layer is None and \
                isinstance(layer, MarkerMapLayer):
            self._default_marker_layer = layer
        self._layers.append(layer)
        c = self.canvas
        if mode == "scatter":
            self.canvas = self.canvas_layers
        else:
            self.canvas = self.canvas_layers_out
        layer.canvas_parent = self.canvas
        super(MapView, self).add_widget(layer)
        self.canvas = c

    def remove_layer(self, layer):
        """Remove the layer
        """
        c = self.canvas
        self._layers.remove(layer)
        self.canvas = layer.canvas_parent
        super(MapView, self).remove_widget(layer)
        self.canvas = c

    def sync_to(self, other):
        """Reflect the lat/lon/zoom of the other MapView to the current one.
        """
        if self._zoom != other._zoom:
            self.set_zoom_at(other._zoom, *self.center)
        self.center_on(other.get_latlon_at(*self.center))

    # Private API

    def __init__(self, **kwargs):
        from kivy.base import EventLoop
        EventLoop.ensure_window()
        self._invalid_scale = True
        self._tiles = []
        self._tiles_bg = []
        self._tilemap = {}
        self._layers = []
        self._default_marker_layer = None
        self._need_redraw_all = False
        self._transform_lock = False
        self.trigger_update(True)
        self.canvas = Canvas()
        self._scatter = MapViewScatter()
        self.add_widget(self._scatter)
        with self._scatter.canvas:
            self.canvas_map = Canvas()
            self.canvas_layers = Canvas()
        with self.canvas:
            self.canvas_layers_out = Canvas()
        self._scale_target_anim = False
        self._scale_target = 1.
        self._touch_count = 0
        self.map_source.cache_dir = self.cache_dir
        Clock.schedule_interval(self._animate_color, 1 / 60.)
        self.lat = kwargs.get("lat", self.lat)
        self.lon = kwargs.get("lon", self.lon)
        super(MapView, self).__init__(**kwargs)

    def _animate_color(self, dt):
        # fast path
        d = self.animation_duration
        if d == 0:
            for tile in self._tiles:
                if tile.state == "need-animation":
                    tile.g_color.a = 1.
                    tile.state = "animated"
            for tile in self._tiles_bg:
                if tile.state == "need-animation":
                    tile.g_color.a = 1.
                    tile.state = "animated"
        else:
            d = d / 1000.
            for tile in self._tiles:
                if tile.state != "need-animation":
                    continue
                tile.g_color.a += dt / d
                if tile.g_color.a >= 1:
                    tile.state = "animated"
            for tile in self._tiles_bg:
                if tile.state != "need-animation":
                    continue
                tile.g_color.a += dt / d
                if tile.g_color.a >= 1:
                    tile.state = "animated"

    def add_widget(self, widget):
        if isinstance(widget, MapMarker):
            self.add_marker(widget)
        elif isinstance(widget, MapLayer):
            self.add_layer(widget)
        else:
            super(MapView, self).add_widget(widget)

    def remove_widget(self, widget):
        if isinstance(widget, MapMarker):
            self.remove_marker(widget)
        elif isinstance(widget, MapLayer):
            self.remove_layer(widget)
        else:
            super(MapView, self).remove_widget(widget)

    def on_map_relocated(self, zoom, coord):
        pass

    def animated_diff_scale_at(self, d, x, y):
        self._scale_target_time = 1.
        self._scale_target_pos = x, y
        if self._scale_target_anim == False:
            self._scale_target_anim = True
            self._scale_target = d
        else:
            self._scale_target += d
        Clock.unschedule(self._animate_scale)
        Clock.schedule_interval(self._animate_scale, 1 / 60.)

    def _animate_scale(self, dt):
        diff = self._scale_target / 3.
        if abs(diff) < 0.01:
            diff = self._scale_target
            self._scale_target = 0
        else:
            self._scale_target -= diff
        self._scale_target_time -= dt
        self.diff_scale_at(diff, *self._scale_target_pos)
        ret = self._scale_target != 0
        if not ret:
            self._pause = False
        return ret

    def diff_scale_at(self, d, x, y):
        scatter = self._scatter
        scale = scatter.scale * (2**d)
        self.scale_at(scale, x, y)

    def scale_at(self, scale, x, y):
        scatter = self._scatter
        scale = clamp(scale, scatter.scale_min, scatter.scale_max)
        rescale = scale * 1.0 / scatter.scale
        scatter.apply_transform(Matrix().scale(rescale, rescale, rescale),
                                post_multiply=True,
                                anchor=scatter.to_local(x, y))

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            return
        if self.pause_on_action:
            self._pause = True
        if "button" in touch.profile and touch.button in ("scrolldown",
                                                          "scrollup"):
            d = 1 if touch.button == "scrollup" else -1
            self.animated_diff_scale_at(d, *touch.pos)
            return True
        elif touch.is_double_tap and self.double_tap_zoom:
            self.animated_diff_scale_at(1, *touch.pos)
            return True
        touch.grab(self)
        self._touch_count += 1
        if self._touch_count == 1:
            self._touch_zoom = (self.zoom, self._scale)
        return super(MapView, self).on_touch_down(touch)

    def on_touch_up(self, touch):
        if touch.grab_current == self:
            touch.ungrab(self)
            self._touch_count -= 1
            if self._touch_count == 0:
                # animate to the closest zoom
                zoom, scale = self._touch_zoom
                cur_zoom = self.zoom
                cur_scale = self._scale
                if cur_zoom < zoom or cur_scale < scale:
                    self.animated_diff_scale_at(1. - cur_scale, *touch.pos)
                elif cur_zoom > zoom or cur_scale > scale:
                    self.animated_diff_scale_at(2. - cur_scale, *touch.pos)
                self._pause = False
            return True
        return super(MapView, self).on_touch_up(touch)

    def on_transform(self, *args):
        self._invalid_scale = True
        if self._transform_lock:
            return
        self._transform_lock = True
        # recalculate viewport
        map_source = self.map_source
        zoom = self._zoom
        scatter = self._scatter
        scale = scatter.scale
        if scale >= 2.:
            zoom += 1
            scale /= 2.
        elif scale < 1:
            zoom -= 1
            scale *= 2.
        zoom = clamp(zoom, map_source.min_zoom, map_source.max_zoom)
        if zoom != self._zoom:
            self.set_zoom_at(zoom, scatter.x, scatter.y, scale=scale)
            self.trigger_update(True)
        else:
            if zoom == map_source.min_zoom and scatter.scale < 1.:
                scatter.scale = 1.
                self.trigger_update(True)
            else:
                self.trigger_update(False)

        if map_source.bounds:
            self._apply_bounds()
        self._transform_lock = False
        self._scale = self._scatter.scale

    def _apply_bounds(self):
        # if the map_source have any constraints, apply them here.
        map_source = self.map_source
        zoom = self._zoom
        min_lon, min_lat, max_lon, max_lat = map_source.bounds
        xmin = map_source.get_x(zoom, min_lon)
        xmax = map_source.get_x(zoom, max_lon)
        ymin = map_source.get_y(zoom, min_lat)
        ymax = map_source.get_y(zoom, max_lat)

        dx = self.delta_x
        dy = self.delta_y
        oxmin, oymin = self._scatter.to_local(self.x, self.y)
        oxmax, oymax = self._scatter.to_local(self.right, self.top)
        s = self._scale
        cxmin = (oxmin - dx)
        if cxmin < xmin:
            self._scatter.x += (cxmin - xmin) * s
        cymin = (oymin - dy)
        if cymin < ymin:
            self._scatter.y += (cymin - ymin) * s
        cxmax = (oxmax - dx)
        if cxmax > xmax:
            self._scatter.x -= (xmax - cxmax) * s
        cymax = (oymax - dy)
        if cymax > ymax:
            self._scatter.y -= (ymax - cymax) * s

    def on__pause(self, instance, value):
        if not value:
            self.trigger_update(True)

    def trigger_update(self, full):
        self._need_redraw_full = full or self._need_redraw_full
        Clock.unschedule(self.do_update)
        Clock.schedule_once(self.do_update, -1)

    def do_update(self, dt):
        zoom = self._zoom
        scale = self._scale
        self.lon = self.map_source.get_lon(
            zoom, (self.center_x - self._scatter.x) / scale - self.delta_x)
        self.lat = self.map_source.get_lat(
            zoom, (self.center_y - self._scatter.y) / scale - self.delta_y)
        self.dispatch("on_map_relocated", zoom, Coordinate(self.lon, self.lat))
        for layer in self._layers:
            layer.reposition()

        if self._need_redraw_full:
            self._need_redraw_full = False
            self.move_tiles_to_background()
            self.load_visible_tiles()
        else:
            self.load_visible_tiles()

    def bbox_for_zoom(self, vx, vy, w, h, zoom):
        # return a tile-bbox for the zoom
        map_source = self.map_source
        size = map_source.dp_tile_size
        scale = self._scale

        max_x_end = map_source.get_col_count(zoom)
        max_y_end = map_source.get_row_count(zoom)

        x_count = int(ceil(w / scale / float(size))) + 1
        y_count = int(ceil(h / scale / float(size))) + 1

        tile_x_first = int(clamp(vx / float(size), 0, max_x_end))
        tile_y_first = int(clamp(vy / float(size), 0, max_y_end))
        tile_x_last = tile_x_first + x_count
        tile_y_last = tile_y_first + y_count
        tile_x_last = int(clamp(tile_x_last, tile_x_first, max_x_end))
        tile_y_last = int(clamp(tile_y_last, tile_y_first, max_y_end))

        x_count = tile_x_last - tile_x_first
        y_count = tile_y_last - tile_y_first
        return (tile_x_first, tile_y_first, tile_x_last, tile_y_last, x_count,
                y_count)

    def load_visible_tiles(self):
        map_source = self.map_source
        vx, vy = self.viewport_pos
        zoom = self._zoom
        dirs = [0, 1, 0, -1, 0]
        bbox_for_zoom = self.bbox_for_zoom
        size = map_source.dp_tile_size

        tile_x_first, tile_y_first, tile_x_last, tile_y_last, \
        x_count, y_count = bbox_for_zoom(vx, vy, self.width, self.height, zoom)

        # print "Range {},{} to {},{}".format(
        #    tile_x_first, tile_y_first,
        #    tile_x_last, tile_y_last)

        # Adjust tiles behind us
        for tile in self._tiles_bg[:]:
            tile_x = tile.tile_x
            tile_y = tile.tile_y

            f = 2**(zoom - tile.zoom)
            w = self.width / f
            h = self.height / f
            btile_x_first, btile_y_first, btile_x_last, btile_y_last, \
            _, _ = bbox_for_zoom(vx / f, vy / f, w, h, tile.zoom)

            if tile_x < btile_x_first or tile_x >= btile_x_last or \
                            tile_y < btile_y_first or tile_y >= btile_y_last:
                tile.state = "done"
                self._tiles_bg.remove(tile)
                self.canvas_map.before.remove(tile.g_color)
                self.canvas_map.before.remove(tile)
                continue

            tsize = size * f
            tile.size = tsize, tsize
            tile.pos = (tile_x * tsize + self.delta_x,
                        tile_y * tsize + self.delta_y)

        # Get rid of old tiles first
        for tile in self._tiles[:]:
            tile_x = tile.tile_x
            tile_y = tile.tile_y

            if tile_x < tile_x_first or tile_x >= tile_x_last or \
                            tile_y < tile_y_first or tile_y >= tile_y_last:
                tile.state = "done"
                self.tile_map_set(tile_x, tile_y, False)
                self._tiles.remove(tile)
                self.canvas_map.remove(tile)
                self.canvas_map.remove(tile.g_color)
            else:
                tile.size = (size, size)
                tile.pos = (tile_x * size + self.delta_x,
                            tile_y * size + self.delta_y)

        # Load new tiles if needed
        x = tile_x_first + x_count // 2 - 1
        y = tile_y_first + y_count // 2 - 1
        arm_max = max(x_count, y_count) + 2
        arm_size = 1
        turn = 0
        while arm_size < arm_max:
            for i in range(arm_size):
                if not self.tile_in_tile_map(x, y) and \
                                y >= tile_y_first and y < tile_y_last and \
                                x >= tile_x_first and x < tile_x_last:
                    self.load_tile(x, y, size, zoom)

                x += dirs[turn % 4 + 1]
                y += dirs[turn % 4]

            if turn % 2 == 1:
                arm_size += 1

            turn += 1

    def load_tile(self, x, y, size, zoom):
        if self.tile_in_tile_map(x, y) or zoom != self._zoom:
            return
        self.load_tile_for_source(self.map_source, 1., size, x, y, zoom)
        # XXX do overlay support
        self.tile_map_set(x, y, True)

    def load_tile_for_source(self, map_source, opacity, size, x, y, zoom):
        tile = Tile(size=(size, size), cache_dir=self.cache_dir)
        tile.g_color = Color(1, 1, 1, 0)
        tile.tile_x = x
        tile.tile_y = y
        tile.zoom = zoom
        tile.pos = (x * size + self.delta_x, y * size + self.delta_y)
        tile.map_source = map_source
        tile.state = "loading"
        if not self._pause:
            map_source.fill_tile(tile)
        self.canvas_map.add(tile.g_color)
        self.canvas_map.add(tile)
        self._tiles.append(tile)

    def move_tiles_to_background(self):
        # remove all the tiles of the main map to the background map
        # retain only the one who are on the current zoom level
        # for all the tile in the background, stop the download if not yet started.
        zoom = self._zoom
        tiles = self._tiles
        btiles = self._tiles_bg
        canvas_map = self.canvas_map
        tile_size = self.map_source.tile_size

        # move all tiles to background
        while tiles:
            tile = tiles.pop()
            if tile.state == "loading":
                tile.state = "done"
                continue
            btiles.append(tile)

        # clear the canvas
        canvas_map.clear()
        canvas_map.before.clear()
        self._tilemap = {}

        # unsure if it's really needed, i personnally didn't get issues right now
        # btiles.sort(key=lambda z: -z.zoom)

        # add all the btiles into the back canvas.
        # except for the tiles that are owned by the current zoom level
        for tile in btiles[:]:
            if tile.zoom == zoom:
                btiles.remove(tile)
                tiles.append(tile)
                tile.size = tile_size, tile_size
                canvas_map.add(tile.g_color)
                canvas_map.add(tile)
                self.tile_map_set(tile.tile_x, tile.tile_y, True)
                continue
            canvas_map.before.add(tile.g_color)
            canvas_map.before.add(tile)

    def remove_all_tiles(self):
        # clear the map of all tiles.
        self.canvas_map.clear()
        self.canvas_map.before.clear()
        for tile in self._tiles:
            tile.state = "done"
        del self._tiles[:]
        del self._tiles_bg[:]
        self._tilemap = {}

    def tile_map_set(self, tile_x, tile_y, value):
        key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
        if value:
            self._tilemap[key] = value
        else:
            self._tilemap.pop(key, None)

    def tile_in_tile_map(self, tile_x, tile_y):
        key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
        return key in self._tilemap

    def on_size(self, instance, size):
        for layer in self._layers:
            layer.size = size
        self.center_on(self.lat, self.lon)
        self.trigger_update(True)

    def on_pos(self, instance, pos):
        self.center_on(self.lat, self.lon)
        self.trigger_update(True)

    def on_map_source(self, instance, source):
        if isinstance(source, string_types):
            self.map_source = MapSource.from_provider(source)
        elif isinstance(source, (tuple, list)):
            cache_key, min_zoom, max_zoom, url, attribution, options = source
            self.map_source = MapSource(url=url,
                                        cache_key=cache_key,
                                        min_zoom=min_zoom,
                                        max_zoom=max_zoom,
                                        attribution=attribution,
                                        cache_dir=self.cache_dir,
                                        **options)
        elif isinstance(source, MapSource):
            self.map_source = source
        else:
            raise Exception("Invalid map source provider")
        self.zoom = clamp(self.zoom, self.map_source.min_zoom,
                          self.map_source.max_zoom)
        self.remove_all_tiles()
        self.trigger_update(True)
Beispiel #3
0
class Toggle1(ToggleButton):

    correctionValues = ()

    newColor = ListProperty([0, 1, 0, 1])

    tickmarckColor1 = ListProperty([51 / 255, 164 / 255, 208 / 255, 1])
    tickmarckColor2 = ListProperty([51 / 255, 164 / 255, 208 / 255, 1])

    currentPower = NumericProperty(0)

    setTemperature = NumericProperty(28)
    setTime = NumericProperty(5)
    setTimeAngle = NumericProperty()

    setTemperature2 = NumericProperty(28)
    setTime2 = NumericProperty(5)
    setTimeAngle2 = NumericProperty()

    lapsedTime = NumericProperty()
    lapsedTime2 = NumericProperty()

    labelOffsetY = -1
    labelOffsetX = -1
    currentTimeAngle = NumericProperty(0)
    seconds = 0
    toggled = False
    running = False

    startingTime = .0

    targetTemperature = 28

    isTimePoint2On = False

    def setTimeZero(self):

        self.running = True
        self.ids.running.disabled = False
        self.ids.running.text = 'running'
        self.startingTime = time.time()
        # print(self.startingTime)
        self.tickmarckColor1 = ([51 / 255, 164 / 255, 208 / 255, 1])
        self.tickmarckColor2 = ([51 / 255, 164 / 255, 208 / 255, 0])

    def updateTime(self):

        if self.running:
            self.seconds = round(time.time() - self.startingTime)

        if self.setTime2 == 0:
            self.currentTimeAngle = 0
            self.targetTemperature = self.setTemperature
        else:
            self.currentTimeAngle = ((310 / self.setTime2) / 60) * self.seconds
            self.setTimeAngle = (self.setTime / self.setTime2) * 310

    #  print(self.setTime)

    #### Timer Updater #####

        if (self.seconds <= self.setTime * 60):
            # self.lapsedTime = round((self.setTime*60 - self.seconds)/60,2)
            self.lapsedTime = self.setTime * 60 - self.seconds
            #self.lapsedTime2 = round((self.setTime2*60 - self.seconds)/60,2)
            self.lapsedTime2 = self.setTime2 * 60 - self.seconds

            self.targetTemperature = 28

        if (self.seconds > self.setTime * 60):
            self.lapsedTime2 = self.setTime2 * 60 - self.seconds
            #self.lapsedTime = round((self.setTime2*60 - self.seconds)/60)
            self.tickmarckColor1 = ([60 / 255, 60 / 255, 60 / 255, 1])
            self.ids.labelTime1.text = ''

            self.targetTemperature = self.setTemperature

        if (self.isTimePoint2On == False):
            self.tickmarckColor2 = ([51 / 255, 164 / 255, 208 / 255, 0])
            self.ids.onTimePoint2.disabled = True
            self.setTimeAngle = 310
            self.ids.labelTime2.text = ''

            if (self.seconds > self.setTime * 60):

                self.tickmarckColor1 = ([60 / 255, 60 / 255, 60 / 255, 1])
                self.ids.labelTime1.text = 'Protocol Completed'

                self.targetTemperature = self.setTemperature

        if (self.isTimePoint2On == True):
            self.tickmarckColor2 = ([51 / 255, 164 / 255, 208 / 255, 1])
            self.ids.onTimePoint2.disabled = False

            if (self.seconds >= self.setTime2 * 60):
                self.tickmarckColor2 = ([60 / 255, 60 / 255, 60 / 255, 1])
                self.lapsedTime2 = 0
                self.targetTemperature = self.setTemperature2
                self.ids.labelTime2.text = 'Protocol Completed'

        # if ((self.seconds + 15)//30)%2 !=  0:
        #     self.labelOffsetY = 1
        # if   ((self.seconds+15)//30)%2 ==  0:
        #     self.labelOffsetY = -1

        # if ((self.seconds )//30)%2 !=  0:
        #     self.labelOffsetX = 1

        # if   ((self.seconds)//30)%2 ==  0:
        #     self.labelOffsetX = -1

    # print (MyPopup().ids.timePoint2Active.active)

    def startStop(self):

        # app.Starter.refreshStarter()
        app.root.ids.newStarter.refreshStarter()
        app.root.ids.editToggle.refreshEditer()
        if self.toggled == False:
            self.toggled = True
        else:
            self.toggled = False

    #  if self.running:

    #       Starter.state = 'down'
    #   if self.running== False:
    #      Starter.state !='down'
    ####a ADD REFRESH EDIT BUTTON

    def BinomialCorrection(self, trueSetTemp):

        a, b, c = self.correctionValues
        return trueSetTemp + (a * trueSetTemp**2 + b * trueSetTemp + c)
Beispiel #4
0
class ItemCircles(ThemableBehavior, Widget):
    _circles_color = ListProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
class CircularNumberPicker(CircularLayout):
    """A circular number picker based on CircularLayout. A selector will
    help you pick a number. You can also set :attr:`multiples_of` to make
    it show only some numbers and use the space in between for the other
    numbers.
    """

    min = NumericProperty(0)
    """The first value of the range.

    :attr:`min` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    """

    max = NumericProperty(0)
    """The last value of the range. Note that it behaves like xrange, so
    the actual last displayed value will be :attr:`max` - 1.

    :attr:`max` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    """

    range = ReferenceListProperty(min, max)
    """Packs :attr:`min` and :attr:`max` into a list for convenience. See
    their documentation for further information.

    :attr:`range` is a :class:`~kivy.properties.ReferenceListProperty`.
    """

    multiples_of = NumericProperty(1)
    """Only show numbers that are multiples of this number. The other numbers
    will be selectable, but won't have their own label.

    :attr:`multiples_of` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    """

    # selector_color = ListProperty([.337, .439, .490])
    selector_color = ListProperty([1, 1, 1])
    """Color of the number selector. RGB.

    :attr:`selector_color` is a :class:`~kivy.properties.ListProperty` and
    defaults to [.337, .439, .490] (material green).
    """

    color = ListProperty([0, 0, 0])
    """Color of the number labels and of the center dot. RGB.

    :attr:`color` is a :class:`~kivy.properties.ListProperty` and
    defaults to [1, 1, 1] (white).
    """

    selector_alpha = BoundedNumericProperty(.3, min=0, max=1)
    """Alpha value for the transparent parts of the selector.

    :attr:`selector_alpha` is a :class:`~kivy.properties.BoundedNumericProperty` and
    defaults to 0.3 (min=0, max=1).
    """

    selected = NumericProperty(None)
    """Currently selected number.

    :attr:`selected` is a :class:`~kivy.properties.NumericProperty` and
    defaults to :attr:`min`.
    """

    number_size_factor = NumericProperty(.5)
    """Font size scale factor fot the :class:`Number`s.

    :attr:`number_size_factor` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.5.
    """

    number_format_string = StringProperty("{}")
    """String that will be formatted with the selected number as the first argument.
    Can be anything supported by :meth:`str.format` (es. "{:02d}").

    :attr:`number_format_string` is a :class:`~kivy.properties.StringProperty` and
    defaults to "{}".
    """

    scale = NumericProperty(1)
    """Canvas scale factor. Used in :class:`CircularTimePicker` transitions.

    :attr:`scale` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    """

    _selection_circle = ObjectProperty(None)
    _selection_line = ObjectProperty(None)
    _selection_dot = ObjectProperty(None)
    _selection_dot_color = ObjectProperty(None)
    _selection_color = ObjectProperty(None)
    _center_dot = ObjectProperty(None)
    _center_color = ObjectProperty(None)

    def _get_items(self):
        return self.max - self.min

    items = AliasProperty(_get_items, None)

    def _get_shown_items(self):
        sh = 0
        for i in xrange(*self.range):
            if i % self.multiples_of == 0:
                sh += 1
        return sh

    shown_items = AliasProperty(_get_shown_items, None)

    def __init__(self, **kw):
        self._trigger_genitems = Clock.create_trigger(self._genitems, -1)
        self.bind(min=self._trigger_genitems,
                  max=self._trigger_genitems,
                  multiples_of=self._trigger_genitems)
        super(CircularNumberPicker, self).__init__(**kw)
        self.selected = self.min
        self.bind(selected=self.on_selected,
                  pos=self.on_selected,
                  size=self.on_selected)

        cx = self.center_x + self.padding[0] - self.padding[2]
        cy = self.center_y + self.padding[3] - self.padding[1]
        sx, sy = self.pos_for_number(self.selected)
        epos = [
            i - (self.delta_radii * self.number_size_factor) for i in (sx, sy)
        ]
        esize = [self.delta_radii * self.number_size_factor * 2] * 2
        dsize = [i * .3 for i in esize]
        dpos = [i + esize[0] / 2. - dsize[0] / 2. for i in epos]
        csize = [i * .05 for i in esize]
        cpos = [i - csize[0] / 2. for i in (cx, cy)]
        dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1
        color = list(self.selector_color)

        with self.canvas:
            self._selection_color = Color(*(color + [self.selector_alpha]))
            self._selection_circle = Ellipse(pos=epos, size=esize)
            self._selection_line = Line(points=[cx, cy, sx, sy],
                                        width=dp(1.25))
            self._selection_dot_color = Color(*(color + [dot_alpha]))
            self._selection_dot = Ellipse(pos=dpos, size=dsize)
            self._center_color = Color(*self.color)
            self._center_dot = Ellipse(pos=cpos, size=csize)

        self.bind(selector_color=lambda ign, u: setattr(
            self._selection_color, "rgba", u + [self.selector_alpha]))
        self.bind(selector_color=lambda ign, u: setattr(
            self._selection_dot_color, "rgb", u))
        self.bind(selector_color=lambda ign, u: self.dot_is_none())
        self.bind(color=lambda ign, u: setattr(self._center_color, "rgb", u))
        Clock.schedule_once(self._genitems)
        Clock.schedule_once(
            self.on_selected)  # Just to make sure pos/size are set

    def dot_is_none(self, *args):
        dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1
        if self._selection_dot_color:
            self._selection_dot_color.a = dot_alpha

    def _genitems(self, *a):
        self.clear_widgets()
        for i in xrange(*self.range):
            if i % self.multiples_of != 0:
                continue
            n = Number(text=self.number_format_string.format(i),
                       size_factor=self.number_size_factor,
                       color=self.color)
            self.bind(color=n.setter("color"))
            self.add_widget(n)

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            return
        touch.grab(self)
        self.selected = self.number_at_pos(*touch.pos)
        if self.selected == 60:
            self.selected = 0

    def on_touch_move(self, touch):
        if touch.grab_current is not self:
            return super(CircularNumberPicker, self).on_touch_move(touch)
        self.selected = self.number_at_pos(*touch.pos)
        if self.selected == 60:
            self.selected = 0

    def on_touch_up(self, touch):
        if touch.grab_current is not self:
            return super(CircularNumberPicker, self).on_touch_up(touch)
        touch.ungrab(self)

    def on_selected(self, *a):
        cx = self.center_x + self.padding[0] - self.padding[2]
        cy = self.center_y + self.padding[3] - self.padding[1]
        sx, sy = self.pos_for_number(self.selected)
        epos = [
            i - (self.delta_radii * self.number_size_factor) for i in (sx, sy)
        ]
        esize = [self.delta_radii * self.number_size_factor * 2] * 2
        dsize = [i * .3 for i in esize]
        dpos = [i + esize[0] / 2. - dsize[0] / 2. for i in epos]
        csize = [i * .05 for i in esize]
        cpos = [i - csize[0] / 2. for i in (cx, cy)]
        dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1

        if self._selection_circle:
            self._selection_circle.pos = epos
            self._selection_circle.size = esize
        if self._selection_line:
            self._selection_line.points = [cx, cy, sx, sy]
        if self._selection_dot:
            self._selection_dot.pos = dpos
            self._selection_dot.size = dsize
        if self._selection_dot_color:
            self._selection_dot_color.a = dot_alpha
        if self._center_dot:
            self._center_dot.pos = cpos
            self._center_dot.size = csize

    def pos_for_number(self, n):
        """Returns the center x, y coordinates for a given number.
        """

        if self.items == 0:
            return 0, 0
        radius = min(self.width - self.padding[0] - self.padding[2],
                     self.height - self.padding[1] - self.padding[3]) / 2.
        middle_r = radius * sum(self.radius_hint) / 2.
        cx = self.center_x + self.padding[0] - self.padding[2]
        cy = self.center_y + self.padding[3] - self.padding[1]
        sign = +1.
        angle_offset = radians(self.start_angle)
        if self.direction == 'cw':
            angle_offset = 2 * pi - angle_offset
            sign = -1.
        quota = 2 * pi / self.items
        mult_quota = 2 * pi / self.shown_items
        angle = angle_offset + n * sign * quota

        if self.items == self.shown_items:
            angle += quota / 2
        else:
            angle -= mult_quota / 2

        # kived: looking it up, yes. x = cos(angle) * radius + centerx; y = sin(angle) * radius + centery
        x = cos(angle) * middle_r + cx
        y = sin(angle) * middle_r + cy

        return x, y

    def number_at_pos(self, x, y):
        """Returns the number at a given x, y position. The number is found
        using the widget's center as a starting point for angle calculations.

        Not thoroughly tested, may yield wrong results.
        """
        if self.items == 0:
            return self.min
        cx = self.center_x + self.padding[0] - self.padding[2]
        cy = self.center_y + self.padding[3] - self.padding[1]
        lx = x - cx
        ly = y - cy
        quota = 2 * pi / self.items
        mult_quota = 2 * pi / self.shown_items
        if lx == 0 and ly > 0:
            angle = pi / 2
        elif lx == 0 and ly < 0:
            angle = 3 * pi / 2
        else:
            angle = atan(ly / lx)
            if lx < 0 < ly:
                angle += pi
            if lx > 0 > ly:
                angle += 2 * pi
            if lx < 0 and ly < 0:
                angle += pi
        angle += radians(self.start_angle)
        if self.direction == "cw":
            angle = 2 * pi - angle
        if mult_quota != quota:
            angle -= mult_quota / 2
        if angle < 0:
            angle += 2 * pi
        elif angle > 2 * pi:
            angle -= 2 * pi

        return int(angle / quota) + self.min
Beispiel #6
0
class TextInputIME(TextInput):

    composition_string = StringProperty()
    sdl_composition = StringProperty()
    composition_window = ObjectProperty()
    candidate_window = ObjectProperty()
    old_cursor_color = ListProperty()
    composition_cursor_index = NumericProperty()

    def __init__(self, **kwargs):

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

        self.disable_on_textedit = (False, False)
        self.is_openIME = False
        self.old_cursor_color = self.cursor_color
        self.old_composition = ''

        EventLoop.window.bind(on_textedit=self._on_textedit)

    def _on_textedit(self, _, value):
        '''
        when there is nothing to be acquired,
        'getEnterdSting','getComposition','getCandidate'
        function returns '\n\n'.
        (It's because I think that IME will not returns '\n\n')

        valiable of value can only retains
        about 15 charactors.

        But 'on_textedit' event is fired when size of composition
        string exceeds 15 too.

        So when fired the event,this function does
        processing such as acquistion candidates.

        返すべき値がないとき、
        'getEnterdSting','getComposition','getCandidate'
        これらの関数は、'\n\n'という文字列を返します。(IMEからこの文字列が
        取得されることがあるとは考えられないからです)

        なおSDL2の制約により、この関数の引数であるvalueには15文字程度以上
        は保持出来ません。しかし、texteditイベント自体は入力中の文字が15文字
        を超えても発火されるため、このイベントが呼ばれたタイミングで
        変換候補取得などの処理をしています。
        '''
        self.sdl_composition = value
        self.is_openIME = bool(dll.getIsOpenIME())

        try:
            entered_text = dll.getEnterdString().decode('cp932')
            composition_string = dll.getComposition().decode('cp932')
            candidates = dll.getCandidate().decode('cp932').split()

            escaped_text = '\n'.join([
                f'[ref={escape_markup(i)}]' + escape_markup(i) + '[/ref]'
                for i in candidates
            ])

            self.candidate_window.escaped_text = escaped_text
            self.candidate_window.text = escaped_text
        except UnicodeError:
            Logger.exception('IME Operator: failed to decode IME information')

        if composition_string != '\n\n':
            self.composition_string = composition_string
        else:
            self.composition_string = ''

        if (entered_text != '\n\n' and self.is_openIME
                and self.old_composition != value):
            index = self.cursor_index()
            self.text = self.text[:index - 1] + \
                entered_text + self.text[index:]
            self.composition_string = ''
            self.old_composition = value
            return None

        self.old_composition = value

    def insert_text(self, substring, from_undo=False):

        if substring == self.sdl_composition:
            return None
        else:
            return super(TextInputIME, self).insert_text(substring, from_undo)

    def keyboard_on_key_down(self, window, keycode, text, modifiers, dt=0):

        cursor_operations = {'left', 'up', 'right', 'down', 'backspace', 'tab'}
        self.composition_cursor_index = len(self.composition_string)

        if keycode[1] == 'left':
            self.composition_cursor_index -= 1

        if keycode[1] == 'right':
            self.composition_cursor_index += 1

        if keycode[1] in cursor_operations and self.composition_string:
            return None

        return super(TextInputIME,
                     self).keyboard_on_key_down(window, keycode, text,
                                                modifiers)

    def on_composition_string(self, _, value):

        if self.composition_string:
            self.cursor_color = (0, 0, 0, 0)
        else:
            self.cursor_color = self.old_cursor_color

        if not dll.getIsOpenIME():
            return

        # this is to underline text.
        # 下線を引くための処理です。
        # https://kivy.org/doc/stable/api-kivy.core.text.markup.html
        self.composition_window.text = '[u]' + value + '[/u]'

    def select_candidate(self, text):

        self.focus = True
        self.readonly = False
        text = text.encode('cp932')
        text = ctypes.create_string_buffer(text)
        dll.setComposition(text)
Beispiel #7
0
class ViViChart(Widget):
    paired_device = StringProperty('No Paired Device')
    n_paired_devices = NumericProperty(0)
    data = ListProperty([])
    recv_stream = ObjectProperty(None)
    #exception = StringProperty(None)
    data_thread = ObjectProperty(None)
    spike_threshold = NumericProperty(None)
    H1 = ListProperty([])
    N1 = ListProperty([])
    
    def __init__(self):
        super(ViViChart, self).__init__()
        graph_theme = {'background_color': 'f8f8f2'}
        self.graph = self.ids.graph
        self.plot = MeshLinePlot(color=[1, 0, 0, 1])
        self.plot.points = []
        self.graph.add_plot(self.plot)
        self.start = time.time()
        
    def discover(self):
        paired_devices = BluetoothAdapter.getDefaultAdapter().getBondedDevices().toArray()
        btns = []
        for device in paired_devices:
            btns.append(Device(text = device.getName()))
        return btns
    
    def search_for_devices(self):
        self.popup=Popup(title='Paired devices', size_hint = [0.8, 0.8],
                         on_open=self.ids.popup2.dismiss)
        btns = self.discover()
        popup_grid = GridLayout(rows = len(btns))
        for btn in btns:        
            popup_grid.add_widget(btn) 
        self.popup.add_widget(popup_grid)
        self.popup.open()
        
    def H1_indices(data, spike_threshold):
        result = []
        for i in xrange(0, len(data)-1):
            if data[i] > spike_threshold and data[i] > data[i - 1] and data[i] >= data[i + 1]:
                result.append(i)
        return numpy.array(result, dtype = int)
    
    # Locate indices for Notch 1 (N1)
    def N1_indices(data, H1):
        result = []
        for i in xrange(0, len(H1) - 1):
            # fist local minimum after H1 as the notch
            pt = next(j for j in xrange(H1[i], H1[i+1]+1) if data[j] <= data[j - 1] and data[j] <= data[j + 1])
            result.append(pt)
        return numpy.array(result, dtype = int)
        
    # Locate indices of P2 peaks
    #def H2_indices(data, H1, N1):
    #    result = []
    #    # in between each spike there is a mini-peak. find it
    #    for i in xrange(0, len(H1)):
    #        # these are subthreshold places where it goes from increasing to decreasing
    #        d = data[H1[i]:N1[i]]
    #        d = signal.savgol_filter(d, 5, 2, 2)
    #        pt = next(j for j in xrange(1,(len(d) - 1)) if d[j-1] >= 0 and d[j+1] <= 0)
    #        result.append(int(range(H1[i],N1[i])[pt]))
    #    return numpy.array(result, dtype = int)
        
    def update(self, outfile, dt):
        
        try:
            BluetoothSocket.isConnected()
        except Exception:
            pass
        else:
            if BluetoothSocket.isConnected() and self.data_thread is None:
                self.data_thread = threading.Thread(target = self.data_logging, args = (outfile,))
                self.data_thread.setDaemon(True)
                self.data_thread.start()
    
                    
        # Plot data in real time
        try:
            self.graph.remove_plot(self.plot)
            #self.data = random.sample(range(0,1024), 300) #mock data
            self.plot.points = zip(range(1, len(self.data)+1), self.data)#[( x, sin(x / 10.)) for x in range(0, int(200*random.random()) )] # This is just some mock data
            self.graph.add_plot(self.plot)
        except Exception:
            pass
        
        try:
            #x = random.sample(xrange(1,100), 10)
            self.spike_threshold = int(numpy.mean(self.data) + numpy.std(self.data))
        except Exception:
            pass
        
        try:
            self.H1 = self.H1_indices(self.data, self.spike_threshold)
        except Exception:
            pass
        
        try:
            self.N1 = self.N1_indices(self.data, self.spike_threshold)
        except Exception:
            pass
        
        
    def data_logging(self, outfile):
        recv_stream = BufferedReader(InputStreamReader(BluetoothSocket.getInputStream()))
        while recv_stream.readLine is not None:
            d = recv_stream.readLine()
            try:
                self.data.append(float(d[:4]))
                self.data = self.data[-300:] #plot the latest 300 data points in real-time
                self.data = numpy.array(self.data)
            except Exception:
                pass
            now = time.time()
            outfile.write(str(now) + "," + str(d)  + "\n")
Beispiel #8
0
class Slug(RelativeLayout):
    name = StringProperty('')
    body_image = StringProperty('')
    eye_image = StringProperty('')
    front_image = StringProperty('')
    y_position = NumericProperty(0)
    wins = NumericProperty(0)
    win_percent = NumericProperty(0)
    odds = NumericProperty(0)
    start_position = NumericProperty(.09)
    finish_position = NumericProperty(.83)
    speeds = ListProperty([])
    rot_angle = NumericProperty(0)
    finished = BooleanProperty(True)
    x_scale = NumericProperty(1)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.rotate_eyes(30, uniform(.6, 1.6))

    def update(self, winning_slug, race_number):
        # odds
        if self == winning_slug:
            self.odds = round(max(1.01, min(self.odds * .96, 20)), 2)
        else:
            self.odds = round(max(1.01, min(self.odds * 1.03, 20)), 2)

        # wins and win_percent
        if self == winning_slug:
            self.wins += 1
        self.win_percent = round(self.wins / race_number * 100, 2)

    def run(self, acceleration=1, backward=False):
        self.finished = False

        move_base = randint(1, 10)
        if move_base < self.speeds[0]:
            running_time = uniform(8, 10) / acceleration  # slow
        elif move_base <= self.speeds[1]:
            running_time = uniform(6, 8) / acceleration  # medium
        else:
            running_time = uniform(4, 6) / acceleration  # fast

        self.eye_animation.cancel(self)
        self.rotate_eyes(30, running_time / 6)

        # If the slug should be running toward the finish line...
        if not backward:
            # ... nothing changes...
            self.run_animation = Animation(
                pos_hint={'x': self.finish_position}, d=running_time)
        # ... but otherwise he should be running to the left and leave the screen.
        else:
            self.run_animation = Animation(pos_hint={'x': -.1}, d=1)

        return self.run_animation

    def reset(self):
        self.pos_hint = {'x': self.start_position}

    def rotate_eyes(self, max_angle, duration):
        self.eye_animation = (Animation(rot_angle=max_angle, d=duration) +
                              Animation(rot_angle=0, d=duration))
        self.eye_animation.repeat = True
        self.eye_animation.start(self)
Beispiel #9
0
class AKAlertDialog(BaseDialog):
    dialog_radius = NumericProperty("10dp")
    bg_color = ListProperty()
    size_portrait = ListProperty(["250dp", "350dp"])
    size_landscape = ListProperty(["400dp", "250dp"])
    header_width_landscape = NumericProperty("110dp")
    header_height_portrait = NumericProperty("110dp")
    fixed_orientation = OptionProperty(None, options=["portrait", "landscape"])
    header_bg = ListProperty()
    header_text_type = OptionProperty("icon", options=["icon", "text"])
    header_text = StringProperty()
    header_icon = StringProperty("android")
    header_color = ListProperty()
    header_h_pos = StringProperty("center")
    header_v_pos = StringProperty("center")
    header_font_size = NumericProperty("55dp")
    progress_interval = NumericProperty(None)
    progress_width = NumericProperty("2dp")
    progress_color = ListProperty()
    elevation = NumericProperty(10)
    content_cls = ObjectProperty()
    opening_duration = NumericProperty(0.2)
    dismiss_duration = NumericProperty(0.2)

    _orientation = StringProperty()
    _progress_value = NumericProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Window.bind(on_resize=self._get_orientation)
        self.register_event_type("on_progress_finish")
        Clock.schedule_once(self._update)

    def _update(self, *args):
        self._get_orientation()
        Window.bind(on_touch_down=self._window_touch_down)
        Window.bind(on_touch_up=self._window_touch_up)
        Window.bind(on_touch_move=self._window_touch_move)

    def _collide_point_with_modal(self, pos):
        if self.attach_to:
            raise NotImplementedError
        for widget in Window.children:
            if issubclass(widget.__class__, ModalView):
                if widget.collide_point(pos[0], pos[1]):
                    return widget
        return False

    def _get_top_modal(self):
        for widget in Window.children:
            if issubclass(widget.__class__, ModalView):
                return widget

    def on_touch_down(self, touch):
        pos = touch.pos
        if self.collide_point(pos[0], pos[1]):
            return super().on_touch_down(touch)
        if self._get_top_modal() == self:
            MDApp.get_running_app().root.dispatch("on_touch_down", touch)
        return super().on_touch_down(touch)

    def on_touch_up(self, touch):
        pos = touch.pos
        if self.collide_point(pos[0], pos[1]):
            return super().on_touch_up(touch)
        if self._get_top_modal() == self:
            MDApp.get_running_app().root.dispatch("on_touch_up", touch)
        return super().on_touch_up(touch)

    def on_touch_move(self, touch):
        pos = touch.pos
        if self.collide_point(pos[0], pos[1]):
            return super().on_touch_move(touch)

        if self._get_top_modal() == self:
            MDApp.get_running_app().root.dispatch("on_touch_move", touch)
        return super().on_touch_move(touch)

    def _window_touch_down(self, instance, touch):
        pos = touch.pos
        collide_modal = self._collide_point_with_modal(pos)
        if collide_modal == self and self._get_top_modal == self:
            return
        if collide_modal == self and self._get_top_modal != self:
            return collide_modal.dispatch("on_touch_down", touch)

    def _window_touch_up(self, instance, touch):
        pos = touch.pos
        collide_modal = self._collide_point_with_modal(pos)
        if collide_modal == self and self._get_top_modal == self:
            return
        if collide_modal == self and self._get_top_modal != self:
            return collide_modal.dispatch("on_touch_up", touch)

    def _window_touch_move(self, instance, touch):
        pos = touch.pos
        collide_modal = self._collide_point_with_modal(pos)
        if collide_modal == self and self._get_top_modal == self:
            return
        if collide_modal == self and self._get_top_modal != self:
            return collide_modal.dispatch("on_touch_move", touch)

    def _get_orientation(self, *args):
        if self.fixed_orientation:
            self._orientation = self.fixed_orientation
        elif self.theme_cls.device_orientation == "portrait":
            self._orientation = "portrait"
        else:
            self._orientation = "landscape"

    def on_content_cls(self, *args):
        if not self.content_cls:
            return

        self.ids.content.clear_widgets()
        self.ids.content.add_widget(self.content_cls)

    def on_open(self):
        self._start_progress()
        return super().on_open()

    def on_pre_open(self):
        self._opening_animation()
        return super().on_pre_open()

    def on_dismiss(self):
        self._dismiss_animation()
        return super().on_dismiss()

    def _opening_animation(self):
        self.opacity = 0
        anim = Animation(opacity=1,
                         duration=self.opening_duration,
                         t="out_quad")
        anim.start(self)

    def _dismiss_animation(self):
        anim = Animation(opacity=0,
                         duration=self.dismiss_duration,
                         t="out_quad")
        anim.start(self)

    def _start_progress(self):
        if not self.progress_interval:
            return
        max_width = self.size[0] - self.dialog_radius * 2
        anim = Animation(_progress_value=max_width,
                         duration=self.progress_interval)
        anim.bind(on_complete=lambda x, y: self.dispatch("on_progress_finish"))
        anim.start(self)

    def on_progress_finish(self, *args):
        pass
Beispiel #10
0
class ThemeManager(EventDispatcher):
    primary_palette = OptionProperty("Blue", options=palette)
    """
    The name of the color scheme that the application will use.
    All major `material` components will have the color
    of the specified color theme.

    Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`,
    `'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`,
    `'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`,
    `'Brown'`, `'Gray'`, `'BlueGray'`.

    To change the color scheme of an application:

    .. code-block:: python

        from kivy.uix.screenmanager import Screen

        from kivymd.app import MDApp
        from kivymd.uix.button import MDRectangleFlatButton


        class MainApp(MDApp):
            def build(self):
                self.theme_cls.primary_palette = "Green"  # "Purple", "Red"

                screen = Screen()
                screen.add_widget(
                    MDRectangleFlatButton(
                        text="Hello, World",
                        pos_hint={"center_x": 0.5, "center_y": 0.5},
                    )
                )
                return screen


        MainApp().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png

    :attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Blue'`.
    """

    primary_hue = OptionProperty("500", options=hue)
    """
    The color hue of the application.

    Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`,
    `'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`.

    To change the hue color scheme of an application:

    .. code-block:: python

        from kivy.uix.screenmanager import Screen

        from kivymd.app import MDApp
        from kivymd.uix.button import MDRectangleFlatButton


        class MainApp(MDApp):
            def build(self):
                self.theme_cls.primary_palette = "Green"  # "Purple", "Red"
                self.theme_cls.primary_hue = "200"  # "500"

                screen = Screen()
                screen.add_widget(
                    MDRectangleFlatButton(
                        text="Hello, World",
                        pos_hint={"center_x": 0.5, "center_y": 0.5},
                    )
                )
                return screen


        MainApp().run()

    With a value of ``self.theme_cls.primary_hue = "500"``:

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png

    With a value of ``self.theme_cls.primary_hue = "200"``:

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-hue.png

    :attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'500'`.
    """

    primary_light_hue = OptionProperty("200", options=hue)
    """
    Hue value for :attr:`primary_light`.

    :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'200'`.
    """

    primary_dark_hue = OptionProperty("700", options=hue)
    """
    Hue value for :attr:`primary_dark`.

    :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'700'`.
    """
    def _get_primary_color(self):
        return get_color_from_hex(
            colors[self.primary_palette][self.primary_hue])

    primary_color = AliasProperty(_get_primary_color,
                                  bind=("primary_palette", "primary_hue"))
    """
    The color of the current application theme in ``rgba`` format.

    :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme, property is readonly.
    """

    def _get_primary_light(self):
        return get_color_from_hex(
            colors[self.primary_palette][self.primary_light_hue])

    primary_light = AliasProperty(_get_primary_light,
                                  bind=("primary_palette",
                                        "primary_light_hue"))
    """
    Colors of the current application color theme in ``rgba`` format
    (in lighter color).

    .. code-block:: python

        from kivy.lang import Builder

        from kivymd.app import MDApp


        KV = '''
        Screen:

            MDRaisedButton:
                text: "primary_light"
                pos_hint: {"center_x": 0.5, "center_y": 0.7}
                md_bg_color: app.theme_cls.primary_light

            MDRaisedButton:
                text: "primary_color"
                pos_hint: {"center_x": 0.5, "center_y": 0.5}

            MDRaisedButton:
                text: "primary_dark"
                pos_hint: {"center_x": 0.5, "center_y": 0.3}
                md_bg_color: app.theme_cls.primary_dark
        '''


        class MainApp(MDApp):
            def build(self):
                self.theme_cls.primary_palette = "Green"
                return Builder.load_string(KV)


        MainApp().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png
        :align: center

    :attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme (in lighter color),
    property is readonly.
    """

    def _get_primary_dark(self):
        return get_color_from_hex(
            colors[self.primary_palette][self.primary_dark_hue])

    primary_dark = AliasProperty(_get_primary_dark,
                                 bind=("primary_palette", "primary_dark_hue"))
    """
    Colors of the current application color theme
    in ``rgba`` format (in darker color).

    :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme (in darker color),
    property is readonly.
    """

    accent_palette = OptionProperty("Amber", options=palette)
    """
    The application color palette used for items such as the tab indicator
    in the :attr:`MDTabsBar` class and so on...

    The image below shows the color schemes with the values
    ``self.theme_cls.accent_palette = 'Blue'``, ``Red'`` and​​ ``Yellow'``:

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-palette.png

    :attr:`accent_palette` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Amber'`.
    """

    accent_hue = OptionProperty("500", options=hue)
    """Similar to :attr:`primary_hue`,
    but returns a value for :attr:`accent_palette`.

    :attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'500'`.
    """

    accent_light_hue = OptionProperty("200", options=hue)
    """
    Hue value for :attr:`accent_light`.

    :attr:`accent_light_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'200'`.
    """

    accent_dark_hue = OptionProperty("700", options=hue)
    """
    Hue value for :attr:`accent_dark`.

    :attr:`accent_dark_hue` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'700'`.
    """

    def _get_accent_color(self):
        return get_color_from_hex(colors[self.accent_palette][self.accent_hue])

    accent_color = AliasProperty(_get_accent_color,
                                 bind=["accent_palette", "accent_hue"])
    """Similar to :attr:`primary_color`,
    but returns a value for :attr:`accent_color`.

    :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_color`,
    property is readonly.
    """

    def _get_accent_light(self):
        return get_color_from_hex(
            colors[self.accent_palette][self.accent_light_hue])

    accent_light = AliasProperty(_get_accent_light,
                                 bind=["accent_palette", "accent_light_hue"])
    """Similar to :attr:`primary_light`,
    but returns a value for :attr:`accent_light`.

    :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_light`,
    property is readonly.
    """

    def _get_accent_dark(self):
        return get_color_from_hex(
            colors[self.accent_palette][self.accent_dark_hue])

    accent_dark = AliasProperty(_get_accent_dark,
                                bind=["accent_palette", "accent_dark_hue"])
    """Similar to :attr:`primary_dark`,
    but returns a value for :attr:`accent_dark`.

    :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_dark`,
    property is readonly.
    """

    theme_style = OptionProperty("Light", options=["Light", "Dark"])
    """App theme style.

    .. code-block:: python

        from kivy.uix.screenmanager import Screen

        from kivymd.app import MDApp
        from kivymd.uix.button import MDRectangleFlatButton


        class MainApp(MDApp):
            def build(self):
                self.theme_cls.theme_style = "Dark"  # "Light"

                screen = Screen()
                screen.add_widget(
                    MDRectangleFlatButton(
                        text="Hello, World",
                        pos_hint={"center_x": 0.5, "center_y": 0.5},
                    )
                )
                return screen


        MainApp().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png

    :attr:`theme_style` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Light'`.
    """

    def _get_theme_style(self, opposite):
        if opposite:
            return "Light" if self.theme_style == "Dark" else "Dark"
        else:
            return self.theme_style

    def _get_bg_darkest(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(colors["Light"]["StatusBar"])
        elif theme_style == "Dark":
            return get_color_from_hex(colors["Dark"]["StatusBar"])

    bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"])
    """
    Similar to :attr:`bg_dark`,
    but the color values ​​are a tone lower (darker) than :attr:`bg_dark`.

    .. code-block:: python

        KV = '''
        <Box@BoxLayout>:
            bg: 0, 0, 0, 0

            canvas:
                Color:
                    rgba: root.bg
                Rectangle:
                    pos: self.pos
                    size: self.size

        BoxLayout:

            Box:
                bg: app.theme_cls.bg_light
            Box:
                bg: app.theme_cls.bg_normal
            Box:
                bg: app.theme_cls.bg_dark
            Box:
                bg: app.theme_cls.bg_darkest
        '''

        from kivy.lang import Builder

        from kivymd.app import MDApp


        class MainApp(MDApp):
            def build(self):
                self.theme_cls.theme_style = "Dark"  # "Light"
                return Builder.load_string(KV)


        MainApp().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png

    :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_darkest`,
    property is readonly.
    """

    def _get_op_bg_darkest(self):
        return self._get_bg_darkest(True)

    opposite_bg_darkest = AliasProperty(_get_op_bg_darkest,
                                        bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_darkest`.

    :attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`,
    property is readonly.
    """

    def _get_bg_dark(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(colors["Light"]["AppBar"])
        elif theme_style == "Dark":
            return get_color_from_hex(colors["Dark"]["AppBar"])

    bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"])
    """
    Similar to :attr:`bg_normal`,
    but the color values ​​are one tone lower (darker) than :attr:`bg_normal`.

    :attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_dark`,
    property is readonly.
    """

    def _get_op_bg_dark(self):
        return self._get_bg_dark(True)

    opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_dark`.

    :attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`opposite_bg_dark`,
    property is readonly.
    """

    def _get_bg_normal(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(colors["Light"]["Background"])
        elif theme_style == "Dark":
            return get_color_from_hex(colors["Dark"]["Background"])

    bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"])
    """
    Similar to :attr:`bg_light`,
    but the color values ​​are one tone lower (darker) than :attr:`bg_light`.

    :attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_normal`,
    property is readonly.
    """

    def _get_op_bg_normal(self):
        return self._get_bg_normal(True)

    opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_normal`.

    :attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`,
    property is readonly.
    """

    def _get_bg_light(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(colors["Light"]["CardsDialogs"])
        elif theme_style == "Dark":
            return get_color_from_hex(colors["Dark"]["CardsDialogs"])

    bg_light = AliasProperty(_get_bg_light, bind=["theme_style"])
    """"
    Depending on the style of the theme (`'Dark'` or `'Light`')
    that the application uses, :attr:`bg_light` contains the color value
    in ``rgba`` format for the widgets background.

    :attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_light`,
    property is readonly.
    """

    def _get_op_bg_light(self):
        return self._get_bg_light(True)

    opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_light`.

    :attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_light`,
    property is readonly.
    """

    def _get_divider_color(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        color[3] = 0.12
        return color

    divider_color = AliasProperty(_get_divider_color, bind=["theme_style"])
    """
    Color for dividing lines such as  :class:`~kivymd.uix.card.MDSeparator`.

    :attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`divider_color`,
    property is readonly.
    """

    def _get_op_divider_color(self):
        return self._get_divider_color(True)

    opposite_divider_color = AliasProperty(_get_op_divider_color,
                                           bind=["theme_style"])
    """
    The opposite value of color in the :attr:`divider_color`.

    :attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_divider_color`,
    property is readonly.
    """

    def _get_text_color(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.87
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        return color

    text_color = AliasProperty(_get_text_color, bind=["theme_style"])
    """
    Color of the text used in the :class:`~kivymd.uix.label.MDLabel`.

    :attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`text_color`,
    property is readonly.
    """

    def _get_op_text_color(self):
        return self._get_text_color(True)

    opposite_text_color = AliasProperty(_get_op_text_color,
                                        bind=["theme_style"])
    """
    The opposite value of color in the :attr:`text_color`.

    :attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_text_color`,
    property is readonly.
    """

    def _get_secondary_text_color(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.54
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
            color[3] = 0.70
        return color

    secondary_text_color = AliasProperty(_get_secondary_text_color,
                                         bind=["theme_style"])
    """
    The color for the secondary text that is used in classes
    from the module :class:`~kivymd/uix/list.TwoLineListItem`.

    :attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`secondary_text_color`,
    property is readonly.
    """

    def _get_op_secondary_text_color(self):
        return self._get_secondary_text_color(True)

    opposite_secondary_text_color = AliasProperty(_get_op_secondary_text_color,
                                                  bind=["theme_style"])
    """
    The opposite value of color in the :attr:`secondary_text_color`.

    :attr:`opposite_secondary_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`opposite_secondary_text_color`,
    property is readonly.
    """

    def _get_icon_color(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.54
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        return color

    icon_color = AliasProperty(_get_icon_color, bind=["theme_style"])
    """
    Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`.

    :attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`icon_color`,
    property is readonly.
    """

    def _get_op_icon_color(self):
        return self._get_icon_color(True)

    opposite_icon_color = AliasProperty(_get_op_icon_color,
                                        bind=["theme_style"])
    """
    The opposite value of color in the :attr:`icon_color`.

    :attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_icon_color`,
    property is readonly.
    """

    def _get_disabled_hint_text_color(self, opposite=False):
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.38
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
            color[3] = 0.50
        return color

    disabled_hint_text_color = AliasProperty(_get_disabled_hint_text_color,
                                             bind=["theme_style"])
    """
    Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.

    :attr:`disabled_hint_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`disabled_hint_text_color`,
    property is readonly.
    """

    def _get_op_disabled_hint_text_color(self):
        return self._get_disabled_hint_text_color(True)

    opposite_disabled_hint_text_color = AliasProperty(
        _get_op_disabled_hint_text_color, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`disabled_hint_text_color`.

    :attr:`opposite_disabled_hint_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`,
    property is readonly.
    """

    # Hardcoded because muh standard
    def _get_error_color(self):
        return get_color_from_hex(colors["Red"]["A700"])

    error_color = AliasProperty(_get_error_color)
    """
    Color of the error text used
    in the :class:`~kivymd.uix.textfield.MDTextField`.

    :attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`error_color`,
    property is readonly.
    """

    def _get_ripple_color(self):
        return self._ripple_color

    def _set_ripple_color(self, value):
        self._ripple_color = value

    _ripple_color = ListProperty(get_color_from_hex(colors["Gray"]["400"]))
    """Private value."""

    ripple_color = AliasProperty(_get_ripple_color,
                                 _set_ripple_color,
                                 bind=["_ripple_color"])
    """
    Color of ripple effects.

    :attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`ripple_color`,
    property is readonly.
    """

    def _determine_device_orientation(self, _, window_size):
        if window_size[0] > window_size[1]:
            self.device_orientation = "landscape"
        elif window_size[1] >= window_size[0]:
            self.device_orientation = "portrait"

    device_orientation = StringProperty("")
    """
    Device orientation.

    :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`.
    """

    def _get_standard_increment(self):
        if DEVICE_TYPE == "mobile":
            if self.device_orientation == "landscape":
                return dp(48)
            else:
                return dp(56)
        else:
            return dp(64)

    standard_increment = AliasProperty(_get_standard_increment,
                                       bind=["device_orientation"])
    """
    Value of standard increment.

    :attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`standard_increment`,
    property is readonly.
    """

    def _get_horizontal_margins(self):
        if DEVICE_TYPE == "mobile":
            return dp(16)
        else:
            return dp(24)

    horizontal_margins = AliasProperty(_get_horizontal_margins)
    """
    Value of horizontal margins.

    :attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`horizontal_margins`,
    property is readonly.
    """

    def on_theme_style(self, instance, value):
        if (hasattr(App.get_running_app(), "theme_cls")
                and App.get_running_app().theme_cls == self):
            self.set_clearcolor_by_theme_style(value)

    set_clearcolor = BooleanProperty(True)

    def set_clearcolor_by_theme_style(self, theme_style):
        if not self.set_clearcolor:
            return
        if theme_style == "Light":
            Window.clearcolor = get_color_from_hex(
                colors["Light"]["Background"])
        elif theme_style == "Dark":
            Window.clearcolor = get_color_from_hex(
                colors["Dark"]["Background"])

    # font name, size (sp), always caps, letter spacing (sp)
    font_styles = DictProperty({
        "H1": ["RobotoLight", 96, False, -1.5],
        "H2": ["RobotoLight", 60, False, -0.5],
        "H3": ["Roboto", 48, False, 0],
        "H4": ["Roboto", 34, False, 0.25],
        "H5": ["Roboto", 24, False, 0],
        "H6": ["RobotoMedium", 20, False, 0.15],
        "Subtitle1": ["Roboto", 16, False, 0.15],
        "Subtitle2": ["RobotoMedium", 14, False, 0.1],
        "Body1": ["Roboto", 16, False, 0.5],
        "Body2": ["Roboto", 14, False, 0.25],
        "Button": ["RobotoMedium", 14, True, 1.25],
        "Caption": ["Roboto", 12, False, 0.4],
        "Overline": ["Roboto", 10, True, 1.5],
        "Icon": ["Icons", 24, False, 0],
    })
    """
    Data of default font styles.

    Add custom font:

    .. code-block:: python

        KV = '''
        Screen:

            MDLabel:
                text: "JetBrainsMono"
                halign: "center"
                font_style: "JetBrainsMono"
        '''

        from kivy.core.text import LabelBase

        from kivy.lang import Builder

        from kivymd.app import MDApp
        from kivymd.font_definitions import theme_font_styles


        class MainApp(MDApp):
            def build(self):
                LabelBase.register(
                    name="JetBrainsMono",
                    fn_regular="JetBrainsMono-Regular.ttf")

                theme_font_styles.append('JetBrainsMono')
                self.theme_cls.font_styles["JetBrainsMono"] = [
                    "JetBrainsMono",
                    16,
                    False,
                    0.15,
                ]
                return Builder.load_string(KV)


        MainApp().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png

    :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas")
        self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas")
        self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas")
        self.round_shadow = Atlas(f"{images_path}round_shadow.atlas")
        Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
        self._determine_device_orientation(None, Window.size)
        Window.bind(size=self._determine_device_orientation)
Beispiel #11
0
class Carousel(StencilView):
    '''Carousel class. See module documentation for more information.
    '''

    slides = ListProperty([])
    '''List of slides inside the Carousel. The slides are the
    widgets added to the Carousel using the :attr:`add_widget` method.

    :attr:`slides` is a :class:`~kivy.properties.ListProperty` and is
    read-only.
    '''
    def _get_slides_container(self):
        return [x.parent for x in self.slides]

    slides_container = AliasProperty(_get_slides_container,
                                     None,
                                     bind=('slides', ))

    direction = OptionProperty('right',
                               options=('right', 'left', 'top', 'bottom'))
    '''Specifies the direction in which the slides are ordered. This
    corresponds to the direction from which the user swipes to go from one
    slide to the next. It
    can be `right`, `left`, `top`, or `bottom`. For example, with
    the default value of `right`, the second slide is to the right
    of the first and the user would swipe from the right towards the
    left to get to the second slide.

    :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'right'.
    '''

    min_move = NumericProperty(0.2)
    '''Defines the minimum distance to be covered before the touch is
    considered a swipe gesture and the Carousel content changed.
    This is a expressed as a fraction of the Carousel's width.
    If the movement doesn't reach this minimum value, the movement is
    cancelled and the content is restored to its original position.

    :attr:`min_move` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.2.
    '''

    anim_move_duration = NumericProperty(0.5)
    '''Defines the duration of the Carousel animation between pages.

    :attr:`anim_move_duration` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 0.5.
    '''

    anim_cancel_duration = NumericProperty(0.3)
    '''Defines the duration of the animation when a swipe movement is not
    accepted. This is generally when the user does not make a large enough
    swipe. See :attr:`min_move`.

    :attr:`anim_cancel_duration` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 0.3.
    '''

    loop = BooleanProperty(False)
    '''Allow the Carousel to loop infinitely. If True, when the user tries to
    swipe beyond last page, it will return to the first. If False, it will
    remain on the last page.

    :attr:`loop` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    def _get_index(self):
        if self.slides:
            return self._index % len(self.slides)
        return None

    def _set_index(self, value):
        if self.slides:
            self._index = value % len(self.slides)
        else:
            self._index = None

    index = AliasProperty(_get_index, _set_index, bind=('_index', 'slides'))
    '''Get/Set the current slide based on the index.

    :attr:`index` is an :class:`~kivy.properties.AliasProperty` and defaults
    to 0 (the first item).
    '''

    def _prev_slide(self):
        slides = self.slides
        len_slides = len(slides)
        index = self.index
        if len_slides < 2:  # None, or 1 slide
            return None
        if len_slides == 2:
            if index == 0:
                return None
            if index == 1:
                return slides[0]
        if self.loop and index == 0:
            return slides[-1]
        if index > 0:
            return slides[index - 1]

    previous_slide = AliasProperty(_prev_slide, None, bind=('slides', 'index'))
    '''The previous slide in the Carousel. It is None if the current slide is
    the first slide in the Carousel. This ordering reflects the order in which
    the slides are added: their presentation varies according to the
    :attr:`direction` property.

    :attr:`previous_slide` is an :class:`~kivy.properties.AliasProperty`.

    .. versionchanged:: 1.5.0
        This property no longer exposes the slides container. It returns
        the widget you have added.
    '''

    def _curr_slide(self):
        if len(self.slides):
            return self.slides[self.index]

    current_slide = AliasProperty(_curr_slide, None, bind=('slides', 'index'))
    '''The currently shown slide.

    :attr:`current_slide` is an :class:`~kivy.properties.AliasProperty`.

    .. versionchanged:: 1.5.0
        The property no longer exposes the slides container. It returns
        the widget you have added.
    '''

    def _next_slide(self):
        if len(self.slides) < 2:  # None, or 1 slide
            return None
        if len(self.slides) == 2:
            if self.index == 0:
                return self.slides[1]
            if self.index == 1:
                return None
        if self.loop and self.index == len(self.slides) - 1:
            return self.slides[0]
        if self.index < len(self.slides) - 1:
            return self.slides[self.index + 1]

    next_slide = AliasProperty(_next_slide, None, bind=('slides', 'index'))
    '''The next slide in the Carousel. It is None if the current slide is
    the last slide in the Carousel. This ordering reflects the order in which
    the slides are added: their presentation varies according to the
    :attr:`direction` property.

    :attr:`next_slide` is an :class:`~kivy.properties.AliasProperty`.

    .. versionchanged:: 1.5.0
        The property no longer exposes the slides container.
        It returns the widget you have added.
    '''

    scroll_timeout = NumericProperty(200)
    '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
    If the user has not moved :attr:`scroll_distance` within the timeout,
    no scrolling will occur and the touch event will go to the children.

    :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 200 (milliseconds)

    .. versionadded:: 1.5.0
    '''

    scroll_distance = NumericProperty('20dp')
    '''Distance to move before scrolling the :class:`Carousel` in pixels. As
    soon as the distance has been traveled, the :class:`Carousel` will start
    to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 20dp.

    .. versionadded:: 1.5.0
    '''

    anim_type = StringProperty('out_quad')
    '''Type of animation to use while animating to the next/previous slide.
    This should be the name of an
    :class:`~kivy.animation.AnimationTransition` function.

    :attr:`anim_type` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'out_quad'.

    .. versionadded:: 1.8.0
    '''

    ignore_perpendicular_swipes = BooleanProperty(False)
    '''Ignore swipes on axis perpendicular to direction.

    :attr:`ignore_perpendicular_swipes` is a
    :class:`~kivy.properties.BooleanProperty` and defaults to False.

    .. versionadded:: 1.10.0
    '''

    # private properties, for internal use only ###
    _index = NumericProperty(0, allownone=True)
    _prev = ObjectProperty(None, allownone=True)
    _current = ObjectProperty(None, allownone=True)
    _next = ObjectProperty(None, allownone=True)
    _offset = NumericProperty(0)
    _touch = ObjectProperty(None, allownone=True)

    _change_touch_mode_ev = None

    def __init__(self, **kwargs):
        self._trigger_position_visible_slides = Clock.create_trigger(
            self._position_visible_slides, -1)
        super(Carousel, self).__init__(**kwargs)
        self._skip_slide = None
        self.touch_mode_change = False

    def load_slide(self, slide):
        '''Animate to the slide that is passed as the argument.

        .. versionchanged:: 1.8.0
        '''
        slides = self.slides
        start, stop = slides.index(self.current_slide), slides.index(slide)
        if start == stop:
            return

        self._skip_slide = stop
        if stop > start:
            self._insert_visible_slides(_next_slide=slide)
            self.load_next()
        else:
            self._insert_visible_slides(_prev_slide=slide)
            self.load_previous()

    def load_previous(self):
        '''Animate to the previous slide.

        .. versionadded:: 1.7.0
        '''
        self.load_next(mode='prev')

    def load_next(self, mode='next'):
        '''Animate to the next slide.

        .. versionadded:: 1.7.0
        '''
        if self.index is not None:
            w, h = self.size
            _direction = {
                'top': -h / 2,
                'bottom': h / 2,
                'left': w / 2,
                'right': -w / 2
            }
            _offset = _direction[self.direction]
            if mode == 'prev':
                _offset = -_offset

            self._start_animation(min_move=0, offset=_offset)

    def get_slide_container(self, slide):
        return slide.parent

    def _insert_visible_slides(self, _next_slide=None, _prev_slide=None):
        get_slide_container = self.get_slide_container

        previous_slide = _prev_slide if _prev_slide else self.previous_slide
        if previous_slide:
            self._prev = get_slide_container(previous_slide)
        else:
            self._prev = None

        current_slide = self.current_slide
        if current_slide:
            self._current = get_slide_container(current_slide)
        else:
            self._current = None

        next_slide = _next_slide if _next_slide else self.next_slide
        if next_slide:
            self._next = get_slide_container(next_slide)
        else:
            self._next = None

        super_remove = super(Carousel, self).remove_widget
        for container in self.slides_container:
            super_remove(container)

        if self._prev and self._prev.parent is not self:
            super(Carousel, self).add_widget(self._prev)
        if self._next and self._next.parent is not self:
            super(Carousel, self).add_widget(self._next)
        if self._current:
            super(Carousel, self).add_widget(self._current)

    def _position_visible_slides(self, *args):
        slides, index = self.slides, self.index
        no_of_slides = len(slides) - 1
        if not slides:
            return
        x, y, width, height = self.x, self.y, self.width, self.height
        _offset, direction = self._offset, self.direction
        _prev, _next, _current = self._prev, self._next, self._current
        get_slide_container = self.get_slide_container
        last_slide = get_slide_container(slides[-1])
        first_slide = get_slide_container(slides[0])
        skip_next = False
        _loop = self.loop

        if direction[0] in ['r', 'l']:
            xoff = x + _offset
            x_prev = {'l': xoff + width, 'r': xoff - width}
            x_next = {'l': xoff - width, 'r': xoff + width}
            if _prev:
                _prev.pos = (x_prev[direction[0]], y)
            elif _loop and _next and index == 0:
                # if first slide is moving to right with direction set to right
                # or toward left with direction set to left
                if ((_offset > 0 and direction[0] == 'r')
                        or (_offset < 0 and direction[0] == 'l')):
                    # put last_slide before first slide
                    last_slide.pos = (x_prev[direction[0]], y)
                    skip_next = True
            if _current:
                _current.pos = (xoff, y)
            if skip_next:
                return
            if _next:
                _next.pos = (x_next[direction[0]], y)
            elif _loop and _prev and index == no_of_slides:
                if ((_offset < 0 and direction[0] == 'r')
                        or (_offset > 0 and direction[0] == 'l')):
                    first_slide.pos = (x_next[direction[0]], y)
        if direction[0] in ['t', 'b']:
            yoff = y + _offset
            y_prev = {'t': yoff - height, 'b': yoff + height}
            y_next = {'t': yoff + height, 'b': yoff - height}
            if _prev:
                _prev.pos = (x, y_prev[direction[0]])
            elif _loop and _next and index == 0:
                if ((_offset > 0 and direction[0] == 't')
                        or (_offset < 0 and direction[0] == 'b')):
                    last_slide.pos = (x, y_prev[direction[0]])
                    skip_next = True
            if _current:
                _current.pos = (x, yoff)
            if skip_next:
                return
            if _next:
                _next.pos = (x, y_next[direction[0]])
            elif _loop and _prev and index == no_of_slides:
                if ((_offset < 0 and direction[0] == 't')
                        or (_offset > 0 and direction[0] == 'b')):
                    first_slide.pos = (x, y_next[direction[0]])

    def on_size(self, *args):
        size = self.size
        for slide in self.slides_container:
            slide.size = size
        self._trigger_position_visible_slides()

    def on_pos(self, *args):
        self._trigger_position_visible_slides()

    def on_index(self, *args):
        self._insert_visible_slides()
        self._trigger_position_visible_slides()
        self._offset = 0

    def on_slides(self, *args):
        if self.slides:
            self.index = self.index % len(self.slides)
        self._insert_visible_slides()
        self._trigger_position_visible_slides()

    def on__offset(self, *args):
        self._trigger_position_visible_slides()
        # if reached full offset, switch index to next or prev
        direction = self.direction
        _offset = self._offset
        width = self.width
        height = self.height
        index = self.index
        if self._skip_slide is not None or index is None:
            return

        if direction[0] == 'r':
            if _offset <= -width:
                index += 1
            if _offset >= width:
                index -= 1
        if direction[0] == 'l':
            if _offset <= -width:
                index -= 1
            if _offset >= width:
                index += 1
        if direction[0] == 't':
            if _offset <= -height:
                index += 1
            if _offset >= height:
                index -= 1
        if direction[0] == 'b':
            if _offset <= -height:
                index -= 1
            if _offset >= height:
                index += 1
        self.index = index

    def _start_animation(self, *args, **kwargs):
        # compute target offset for ease back, next or prev
        new_offset = 0
        direction = kwargs.get('direction', self.direction)
        is_horizontal = direction[0] in ['r', 'l']
        extent = self.width if is_horizontal else self.height
        min_move = kwargs.get('min_move', self.min_move)
        _offset = kwargs.get('offset', self._offset)

        if _offset < min_move * -extent:
            new_offset = -extent
        elif _offset > min_move * extent:
            new_offset = extent

        # if new_offset is 0, it wasnt enough to go next/prev
        dur = self.anim_move_duration
        if new_offset == 0:
            dur = self.anim_cancel_duration

        # detect edge cases if not looping
        len_slides = len(self.slides)
        index = self.index
        if not self.loop or len_slides == 1:
            is_first = (index == 0)
            is_last = (index == len_slides - 1)
            if direction[0] in ['r', 't']:
                towards_prev = (new_offset > 0)
                towards_next = (new_offset < 0)
            else:
                towards_prev = (new_offset < 0)
                towards_next = (new_offset > 0)
            if (is_first and towards_prev) or (is_last and towards_next):
                new_offset = 0

        anim = Animation(_offset=new_offset, d=dur, t=self.anim_type)
        anim.cancel_all(self)

        def _cmp(*l):
            if self._skip_slide is not None:
                self.index = self._skip_slide
                self._skip_slide = None

        anim.bind(on_complete=_cmp)
        anim.start(self)

    def _get_uid(self, prefix='sv'):
        return '{0}.{1}'.format(prefix, self.uid)

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            touch.ud[self._get_uid('cavoid')] = True
            return
        if self.disabled:
            return True
        if self._touch:
            return super(Carousel, self).on_touch_down(touch)
        Animation.cancel_all(self)
        self._touch = touch
        uid = self._get_uid()
        touch.grab(self)
        touch.ud[uid] = {'mode': 'unknown', 'time': touch.time_start}
        self._change_touch_mode_ev = Clock.schedule_once(
            self._change_touch_mode, self.scroll_timeout / 1000.)
        self.touch_mode_change = False
        return True

    def on_touch_move(self, touch):
        if not self.touch_mode_change:
            if self.ignore_perpendicular_swipes and \
                    self.direction in ('top', 'bottom'):
                if abs(touch.oy - touch.y) < self.scroll_distance:
                    if abs(touch.ox - touch.x) > self.scroll_distance:
                        self._change_touch_mode()
                        self.touchModeChange = True
            elif self.ignore_perpendicular_swipes and \
                    self.direction in ('right', 'left'):
                if abs(touch.ox - touch.x) < self.scroll_distance:
                    if abs(touch.oy - touch.y) > self.scroll_distance:
                        self._change_touch_mode()
                        self.touchModeChange = True

        if self._get_uid('cavoid') in touch.ud:
            return
        if self._touch is not touch:
            super(Carousel, self).on_touch_move(touch)
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True
        ud = touch.ud[self._get_uid()]
        direction = self.direction
        if ud['mode'] == 'unknown':
            if direction[0] in ('r', 'l'):
                distance = abs(touch.ox - touch.x)
            else:
                distance = abs(touch.oy - touch.y)
            if distance > self.scroll_distance:
                ev = self._change_touch_mode_ev
                if ev is not None:
                    ev.cancel()
                ud['mode'] = 'scroll'
        else:
            if direction[0] in ('r', 'l'):
                self._offset += touch.dx
            if direction[0] in ('t', 'b'):
                self._offset += touch.dy
        return True

    def on_touch_up(self, touch):
        if self._get_uid('cavoid') in touch.ud:
            return
        if self in [x() for x in touch.grab_list]:
            touch.ungrab(self)
            self._touch = None
            ud = touch.ud[self._get_uid()]
            if ud['mode'] == 'unknown':
                ev = self._change_touch_mode_ev
                if ev is not None:
                    ev.cancel()
                super(Carousel, self).on_touch_down(touch)
                Clock.schedule_once(partial(self._do_touch_up, touch), .1)
            else:
                self._start_animation()

        else:
            if self._touch is not touch and self.uid not in touch.ud:
                super(Carousel, self).on_touch_up(touch)
        return self._get_uid() in touch.ud

    def _do_touch_up(self, touch, *largs):
        super(Carousel, self).on_touch_up(touch)
        # don't forget about grab event!
        for x in touch.grab_list[:]:
            touch.grab_list.remove(x)
            x = x()
            if not x:
                continue
            touch.grab_current = x
            super(Carousel, self).on_touch_up(touch)
        touch.grab_current = None

    def _change_touch_mode(self, *largs):
        if not self._touch:
            return
        self._start_animation()
        uid = self._get_uid()
        touch = self._touch
        ud = touch.ud[uid]
        if ud['mode'] == 'unknown':
            touch.ungrab(self)
            self._touch = None
            super(Carousel, self).on_touch_down(touch)
            return

    def add_widget(self, widget, index=0, canvas=None):
        slide = RelativeLayout(size=self.size, x=self.x - self.width, y=self.y)
        slide.add_widget(widget)
        super(Carousel, self).add_widget(slide, index, canvas)
        if index != 0:
            self.slides.insert(index - len(self.slides), widget)
        else:
            self.slides.append(widget)

    def remove_widget(self, widget, *args, **kwargs):
        # XXX be careful, the widget.parent refer to the RelativeLayout
        # added in add_widget(). But it will break if RelativeLayout
        # implementation change.
        # if we passed the real widget
        if widget in self.slides:
            slide = widget.parent
            self.slides.remove(widget)
            return slide.remove_widget(widget, *args, **kwargs)
        return super(Carousel, self).remove_widget(widget, *args, **kwargs)

    def clear_widgets(self):
        for slide in self.slides[:]:
            self.remove_widget(slide)
        super(Carousel, self).clear_widgets()
Beispiel #12
0
class Jocular(MDApp):

    showing = OptionProperty(
        'main', options=['main', 'observing list', 'calibration', 'observations']
    )

    lowlight_color = ListProperty([0.5, 0.5, 0.5, 1])
    hint_color = ListProperty([0.25, 0.25, 0.25, 1])
    lever_color = ListProperty([0.32, 0.32, 0.32, 1])
    background_color = ListProperty([0.06, 0.06, 0.06, 0])  # was 1 transp
    line_color = ListProperty([0.3, 0.3, 0.3, 1])  # used in Obj and Table only

    ring_font_size = StringProperty('14sp')
    info_font_size = StringProperty('14sp')
    form_font_size = StringProperty('15sp')

    data_dir = StringProperty(None)

    subdirs = {'captures', 'calibration', 'snapshots', 'deleted', 
        'exports', 'catalogues', 'settings', 'logs'}

    brightness = NumericProperty(1)
    transparency = NumericProperty(0)

    def get_dir_in_datadir(self, name):
        path = os.path.join(self.data_dir, name)
        try:
            if not os.path.exists(path):
                logger.debug('Creating path {:}'.format(path))
                os.mkdir(path)
            return os.path.join(self.data_dir, name)
        except Exception as e:
            logger.exception('Cannot create path {:} ({:})'.format(path, e))
            sys.exit('Cannot create subdirectory of Jocular data directory')

    def get_path(self, name):
        # centralised way to handle accessing resources Jocular needs

        #  if path, return path to data dir, else return path to resource
        if name in self.subdirs:
            return self.get_dir_in_datadir(name)

        # jocular's own resources

        elif name == 'dsos':
            return os.path.join(self.directory, 'dsos')

        elif name in {'configurables.json', 'gui.json', 'object_types.json'}:
            return os.path.join(self.directory, 'resources', name)

        # user-accessible settings
        elif name in {'observing_list.json', 'observing_notes.json', 'previous_observations.json'}:
            return os.path.join(self.data_dir, name)

        # jocular settings
        elif name.endswith('.json'):
            return os.path.join(self.get_dir_in_datadir('settings'), name)

        # specific files
        elif name == 'libusb':
            return os.path.join(self.directory, 'resources', 'libusb-1.0.dll')
        elif name == 'star_db':
            return os.path.join(self.data_dir, 'platesolving', 'star_tiles.npz')
        elif name == 'dso_db':
            return os.path.join(self.data_dir, 'platesolving', 'dso_tiles')
        elif name == 'ASI':
            # NB these need keeping up to date
            if sys.platform.startswith('linux'):
                asi = 'libASICamera2.so.1.18'
            elif sys.platform.startswith('darwin'):
                asi = 'libASICamera2.dylib.1.18'
            else:
                asi = 'ASICamera2.dll'
            return os.path.abspath(os.path.join(self.directory, 'resources', asi))

        # everything else is in jocular's own resources
        else:
            return os.path.join(self.directory, 'resources', name)

    def on_brightness(self, *args):
        for c in ['hint_color', 'lowlight_color', 'line_color', 'lever_color']:
            getattr(self, c)[-1] = self.brightness

    @logger.catch()
    def build(self):

        self.data_dir = get_datadir()
        if self.data_dir is not None:
            start_logging(self.get_path('logs'))

        self.title = 'Jocular v{:}'.format(__version__)
        self.theme_cls.theme_style = "Dark"     

        LabelBase.register(name='Jocular', fn_regular=self.get_path('jocular4.ttf'))

        try:
            with open(self.get_path('Appearance.json'), 'r') as f:
                settings = json.load(f)
        except:
            # set up initial values for settings
            settings = {'highlight_color': 'Blue',  'colour_saturation': 50}

        # apply settings      
        for p, v in settings.items():
            if p == 'highlight_color':
                self.theme_cls.accent_palette = v
                self.theme_cls.primary_palette = v
            elif p.endswith('_color'):
                lg = v / 100
                setattr(self, p, [lg, lg, lg, 1])
            elif p.endswith('font_size'):
                setattr(self, p, '{:}sp'.format(v))
            elif p == 'transparency':
                self.transparency = int(v) / 100
            elif p == 'colour_saturation':
                self.theme_cls.accent_hue = sat_to_hue(v)

        self.gui = GUI()

        # draw GUI
        Clock.schedule_once(self.gui.draw, -1)

        return self.gui


    @logger.catch()
    def on_stop(self, exception=None):
        if exception is None:
            logger.info('normal close down')
        else:
            logger.exception('root exception: {:}'.format(exception))
        # save width and height
        Config.set('graphics', 'width', str(int(Window.width/ dp(1))))
        Config.set('graphics', 'height', str(int(Window.height/dp(1))))
        Config.write()
        Component.close()
        self.gui.on_close()
        logger.info('finished close down')

    # reset showing to main when any table is hidden
    def table_hiding(self, *args):
        self.showing = 'main'
class Playground(Widget):
    engine = ObjectProperty()
    game_widgets = ListProperty()
    grid = ObjectProperty()
    target_field_widgets = ListProperty()
    is_target_field = BooleanProperty(False)
    rules_scroll_view = Property(None)
    update_event = ObjectProperty(None, allownone=True)
    storage = ObjectProperty()
    sound_handler = ObjectProperty()
    scroll_views = DictProperty()
    selected_box = ObjectProperty(allownone=True)

    def start(self, lvl):
        self.engine = Engine(lvl, self.sound_handler)
        self.init_storage_star()
        self.add_missing_game_widgets()
        self.draw_field()
        self.set_target_field_widgets()
        self.scroll_views = {}
        self.make_rules_scroll_view(self.engine.get_all_rules(),
                                    lambda _: None, id(self))
        self.update_event = Clock.schedule_interval(self.update,
                                                    FRAME_RATE_SEC)

    def update(self, _):
        self.engine.tick()
        self.add_missing_game_widgets()
        self.update_all_game_widgets()
        self.check_win()

    def update_all_game_widgets(self):
        for game_widget in self.game_widgets:
            corresponding_game_obj = next(
                (obj for obj in self.engine.all_game_objects()
                 if obj.game_id == game_widget.game_id), None)
            if corresponding_game_obj is None:
                self.remove_widget(game_widget)
                self.game_widgets.remove(game_widget)
                continue
            self.update_game_widget(game_widget, corresponding_game_obj)

    @staticmethod
    def update_game_widget(game_widget, game_object):
        game_widget.box = game_object
        for attr, value in game_object.__dict__.items():
            if hasattr(game_widget, attr):
                setattr(game_widget, attr, value)

    def add_missing_game_widgets(self):
        for obj in self.engine.all_game_objects():
            if any(widg.game_id == obj.game_id for widg in self.game_widgets):
                continue
            wimg = BoxWidget(obj)
            self.game_widgets.append(wimg)
            self.add_widget(wimg)

    def check_win(self):
        if self.engine.win and not self.engine.any_animation_in_progress():
            self.manage_star_after_win()
            self.update_storage(f'lvl{self.engine.lvl}', status='Passed')
            if self.storage.get('lvl' + str(self.engine.lvl +
                                            1))['status'] == 'Locked':
                self.update_storage(f'lvl{self.engine.lvl + 1}',
                                    status='Unlocked')
            self.update_event.cancel()
            self.parent.show_winning_widget()

    def manage_star_after_win(self):
        if self.engine.min_moves and self.engine.moves_done <= self.engine.min_moves and \
                not self.storage.get(f'lvl{self.engine.lvl}')['got_star']:
            self.update_storage(f'lvl{self.engine.lvl}', got_star=True)
            cur_module = str(get_module(self.engine.lvl))
            module_stars = self.storage.get('module_stars')
            module_stars[cur_module] += 1
            self.storage.put('module_stars', **module_stars)

    def draw_field(self):
        with self.canvas.before:
            Color(0.992, 0.925, 0.863, 1)
            Rectangle(pos=self.engine.screen_utils.start,
                      size=self.engine.screen_utils.size)

        self.grid = InstructionGroup()
        points = self.engine.screen_utils.create_grid()
        self.grid.add(Color(rgba=(0.29, 0, 0.153, 1)))
        for a, b in points:
            self.grid.add(Line(points=[a[0], a[1], b[0], b[1]]))

        border_width = 5
        dl = self.engine.screen_utils.start
        width, height = self.engine.screen_utils.size
        self.grid.add(
            Line(points=[
                dl[0] - border_width, dl[1] - border_width,
                dl[0] - border_width, dl[1] + height + border_width,
                dl[0] + width + border_width, dl[1] + height + border_width,
                dl[0] + width + border_width, dl[1] - border_width
            ],
                 width=border_width,
                 close=True))
        self.canvas.before.add(self.grid)

    def set_target_field_widgets(self):
        for box in self.engine.get_target_field_boxes():
            box_wimg = Image()
            for attr, value in box.__dict__.items():
                if hasattr(box_wimg, attr):
                    setattr(box_wimg, attr, value)
            self.target_field_widgets.append(box_wimg)

    def switch_field(self):
        self.show_all_rules()
        self.unselect_box()
        if not self.is_target_field:
            for widg in self.game_widgets:
                self.remove_widget(widg)
            for box_wimg in self.target_field_widgets:
                self.add_widget(box_wimg)
        else:
            for widg in self.target_field_widgets:
                self.remove_widget(widg)
            for widg in self.game_widgets:
                self.add_widget(widg)
        self.is_target_field = not self.is_target_field

    def show_all_rules(self):
        self.unselect_box()
        self.make_rules_scroll_view(self.engine.get_all_rules(),
                                    lambda _: None, id(self))

    def make_rules_scroll_view(self, rules, click_on_rule_function, obj_hash):
        if self.rules_scroll_view is not None:
            self.remove_widget(self.rules_scroll_view)
        if obj_hash in self.scroll_views:
            self.rules_scroll_view = self.scroll_views[obj_hash]
            self.add_widget(self.rules_scroll_view)
            return
        self.rules_scroll_view = RulesScrollViewWidget()
        if obj_hash == id(self):
            ''' That means we're working with all rules, so no need in all rules button'''
            self.rules_scroll_view.remove_widget(
                self.rules_scroll_view.all_rules_btn)
        self.add_widget(self.rules_scroll_view)
        if len(rules) == 0:
            from src.LanguageUtils import LanguageUtils
            img = Image(source=LanguageUtils().set_texture('no_rules'),
                        size_hint=(1, None))
            self.rules_scroll_view.ids.grid.add_widget(img)
        else:
            max_right_side_len = max(
                [len(rule.result_box_kinds) for rule in rules] + [1])
            for rule in rules:
                rule_widget = RuleWidget(rule, click_on_rule_function,
                                         max_right_side_len)
                self.rules_scroll_view.ids.grid.add_widget(rule_widget)
        self.scroll_views[obj_hash] = self.rules_scroll_view

    def undo(self):
        if self.is_target_field:
            return
        self.engine.undo()
        self.unselect_box()
        self.show_all_rules()

    def on_touch_down(self, touch):
        if self.engine.any_animation_in_progress():
            self.engine.finish_all_animations()
            return True
        return super(Playground, self).on_touch_down(touch)

    def init_storage_star(self):
        if self.engine.min_moves is None:
            return
        lvl_dict = self.storage.get(f'lvl{self.engine.lvl}')
        if 'got_star' not in lvl_dict.keys():
            lvl_dict['got_star'] = False
            self.storage.put(f'lvl{self.engine.lvl}', **lvl_dict)

    def update_storage(self, key, **kwargs):
        d = self.storage.get(key)
        for key, value in kwargs.items():
            d[key] = value
        self.storage.put(key, **d)

    def select_box(self, box_widg):
        if box_widg not in self.game_widgets:
            return
        if self.selected_box is not None:
            self.selected_box.unselect()
        box_widg.select()
        self.selected_box = box_widg

    def unselect_box(self):
        if self.selected_box is None:
            return
        self.selected_box.unselect()
        self.selected_box = None
Beispiel #14
0
class BaseButton(
        ThemableBehavior,
        ButtonBehavior,
        SpecificBackgroundColorBehavior,
        AnchorLayout,
):
    """
    Abstract base class for all MD buttons. This class handles the button's
    colors (disabled/down colors handled in children classes as those depend on
    type of button) as well as the disabled state.
    """

    _md_bg_color_down = ListProperty(None, allownone=True)
    _md_bg_color_disabled = ListProperty(None, allownone=True)
    _current_button_color = ListProperty([0.0, 0.0, 0.0, 0.0])
    theme_text_color = OptionProperty(
        None,
        allownone=True,
        options=[
            "Primary",
            "Secondary",
            "Hint",
            "Error",
            "Custom",
            "ContrastParentBackground",
        ],
    )
    text_color = ListProperty(None, allownone=True)
    opposite_colors = BooleanProperty(False)
    font_name = StringProperty(None)
    font_size = NumericProperty(14)
    user_font_size = NumericProperty()
    """Custom font size."""
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self._finish_init)

    def _finish_init(self, dt):
        self._update_color()

    def on_md_bg_color(self, instance, value):
        self._update_color()

    def _update_color(self):
        if not self.disabled:
            self._current_button_color = self.md_bg_color
        else:
            self._current_button_color = self.md_bg_color_disabled

    def _call_get_bg_color_down(self):
        return self._get_md_bg_color_down()

    def _get_md_bg_color_down(self):
        if self._md_bg_color_down:
            return self._md_bg_color_down
        else:
            raise NotImplementedError

    def _set_md_bg_color_down(self, value):
        self._md_bg_color_down = value

    md_bg_color_down = AliasProperty(_call_get_bg_color_down,
                                     _set_md_bg_color_down)

    def _call_get_bg_color_disabled(self):
        return self._get_md_bg_color_disabled()

    def _get_md_bg_color_disabled(self):
        if self._md_bg_color_disabled:
            return self._md_bg_color_disabled
        else:
            raise NotImplementedError

    def _set_md_bg_color_disabled(self, value):
        self._md_bg_color_disabled = value

    md_bg_color_disabled = AliasProperty(_call_get_bg_color_disabled,
                                         _set_md_bg_color_disabled)

    def on_disabled(self, instance, value):
        if self.disabled:
            self._current_button_color = self.md_bg_color_disabled
        else:
            self._current_button_color = self.md_bg_color
Beispiel #15
0
class RegisterTable(RecycleView):
    """Displays Information regarding registers"""
    data_list = ListProperty([])

    def __init__(self, **kwargs):
        self.dpi = kwargs.pop('dpi')
        super(RegisterTable, self).__init__(**kwargs)
        self.viewclass = 'Label'
        self.recycle_grid_layout = self.children[0]
        if self.dpi < 192:
            self.pos_hint = {'x': dp(0), 'center_y': dp(0.75)}
            self.size_hint_x = dp(0.2)
            self.size_hint_y = dp(0.5)
            with self.children[0].canvas.before:
                Color(.50, .50, .50, 1)
                for i in range(13):
                    Line(width=2,
                         rectangle=(dp(0), dp(0), dp(205), dp(390 - (30 * i))))
                Line(width=2, rectangle=(dp(0), dp(0), dp(102.5), dp(390)))
        else:
            self.pos_hint = {'x': dp(0), 'center_y': dp(0.368)}
            self.size_hint_x = dp(0.12)
            self.size_hint_y = dp(0.265)
            self.recycle_grid_layout.size_hint_x = dp(0.47)
            with self.children[0].canvas.before:
                Color(.50, .50, .50, 1)
                for i in range(13):
                    Line(width=2,
                         rectangle=(dp(0), dp(0), dp(245), dp(390 - (30 * i))))
                Line(width=2, rectangle=(dp(0), dp(0), dp(115), dp(390)))

    def get_data(self):
        """
        Updates Register Table
        """
        _data_list = self.data_list.copy()
        self.data_list.clear()
        self.data_list.append('REGISTER')
        self.data_list.append('VALUE')
        _data = []
        for k, v in REGISTER.items():
            self.data_list.append(k)
            self.data_list.append(v)

        i = 0
        while i < len(self.data_list):
            if _data_list and len(_data_list) > 2 and _data_list[i] == self.data_list[i] and _data_list[i + 1] != \
                    self.data_list[i + 1]:
                _data.append({
                    'text': self.data_list[i].upper(),
                    'color': (1, 0, 0, 1)
                })
                _data.append({
                    'text': self.data_list[i + 1].upper(),
                    'color': (1, 0, 0, 1)
                })
            else:
                _data.append({
                    'text': self.data_list[i].upper(),
                    'color': (.1, .1, .1, 1)
                })
                _data.append({
                    'text': self.data_list[i + 1].upper(),
                    'color': (.1, .1, .1, 1)
                })
            i += 2
        self.data = _data
Beispiel #16
0
class Spinner(Button):
    '''Spinner class, see module documentation for more information.
    '''

    values = ListProperty()
    '''Values that can be selected by the user. It must be a list of strings.

    :data:`values` is a :class:`~kivy.properties.ListProperty` and defaults to
    [].
    '''

    option_cls = ObjectProperty(SpinnerOption)
    '''Class used to display the options within the dropdown list displayed
    under the Spinner. The `text` property of the class will be used to
    represent the value.

    The option class requires at least:

    - a `text` property, used to display the value.
    - an `on_release` event, used to trigger the option when pressed/touched.

    :data:`option_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`SpinnerOption`.
    '''

    dropdown_cls = ObjectProperty(DropDown)
    '''Class used to display the dropdown list when the Spinner is pressed.

    :data:`dropdown_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`~kivy.uix.dropdown.DropDown`.
    '''

    is_open = BooleanProperty(False)
    '''By default, the spinner is not open. Set to True to open it.

    :data:`is_open` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.

    .. versionadded:: 1.4.0
    '''
    def __init__(self, **kwargs):
        self._dropdown = None
        super(Spinner, self).__init__(**kwargs)
        self.bind(on_release=self._toggle_dropdown,
                  dropdown_cls=self._build_dropdown,
                  option_cls=self._build_dropdown,
                  values=self._update_dropdown)
        self._build_dropdown()

    def _build_dropdown(self, *largs):
        if self._dropdown:
            self._dropdown.unbind(on_select=self._on_dropdown_select)
            self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
            self._dropdown.dismiss()
            self._dropdown = None
        self._dropdown = self.dropdown_cls()
        self._dropdown.bind(on_select=self._on_dropdown_select)
        self._dropdown.bind(on_dismiss=self._toggle_dropdown)
        self._update_dropdown()

    def _update_dropdown(self, *largs):
        dp = self._dropdown
        cls = self.option_cls
        dp.clear_widgets()
        for value in self.values:
            item = cls(text=value)
            item.bind(on_release=lambda option: dp.select(option.text))
            dp.add_widget(item)

    def _toggle_dropdown(self, *largs):
        self.is_open = not self.is_open

    def _on_dropdown_select(self, instance, data, *largs):
        self.text = data
        self.is_open = False

    def on_is_open(self, instance, value):
        if value:
            self._dropdown.open(self)
        else:
            if self._dropdown.attach_to:
                self._dropdown.dismiss()
Beispiel #17
0
class NavigationDrawerIconButton(OneLineIconListItem):
    """An item in the :class:`MDNavigationDrawer`."""

    _active = BooleanProperty(False)
    _active_color = ListProperty()
    _icon = ObjectProperty()
    divider = None

    active_color = ListProperty()
    """Custom active color.
    This option only takes effect when :attr:`active_color_type` = 'custom'.

    :attr:`active_color` is a :class:`~kivy.properties.ListProperty`
    and defaults to None.
    """

    active_color_type = OptionProperty('primary',
                                       options=['primary', 'accent', 'custom'])
    """Decides which color should be used for the active color.
    This option only takes effect when :attr:`use_active` = True.

    Options:
        primary: Active color will be the primary theme color.

        accent: Active color will be the theme's accent color.

        custom: Active color will be taken from the :attr:`active_color` attribute.

    :attr:`active_color_type` is a :class:`~kivy.properties.OptionProperty`
    and defaults to 'primary'.
    """

    icon = StringProperty('checkbox-blank-circle')
    """Icon that appears to the left of the widget.

    :attr:`icon` is a :class:`~kivy.properties.StringProperty` and defaults
    to 'checkbox-blank-circle'.
    """
    badge_text = StringProperty('')
    """
    Text that appears on the right side of the item, usually
    for displaying a count of sorts.


    :attr:`badge_text` is a :class:`~kivy.properties.StringProperty`
    and defaults to ''.
    """
    use_active = BooleanProperty(True)
    """If the button should change to the active color when selected.

    :attr:`use_active` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to True.

    See also:
        :attr:`active_color`

        :attr:`active_color_type`
    """

    # active_color = get_color_from_hex(colors['Red']['500'])
    # active_color_type = 'custom'

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._set_active_color()
        self.theme_cls.bind(primary_color=self._set_active_color_primary,
                            accent_color=self._set_active_color_accent)
        Clock.schedule_once(lambda x: self.on_icon(self, self.icon))

    def _set_active(self, active, nav_drawer):
        if self.use_active:
            self._active = active
            if nav_drawer.active_item != self:
                if nav_drawer.active_item is not None:
                    nav_drawer.active_item._active = False
            nav_drawer.active_item = self

    def _set_active_color(self, *args):
        if self.active_color_type == 'primary':
            self._set_active_color_primary()
        elif self.active_color_type == 'accent':
            self._set_active_color_accent()

    # Note to future developers/myself: These must be separate functions
    def _set_active_color_primary(self, *args):
        if self.active_color_type == 'primary':
            self._active_color = self.theme_cls.primary_color

    def _set_active_color_accent(self, *args):
        if self.active_color_type == 'accent':
            self._active_color = self.theme_cls.accent_color

    def on_icon(self, instance, value):
        super().__init__()
        self.ids._icon.text = u'{}'.format(md_icons[value])

    def on_active_color_type(self, *args):
        self._set_active_color(args)
Beispiel #18
0
class ScrollView(StencilView):
    '''ScrollView class. See module documentation for more information.

    :Events:
        `on_scroll_start`
            Generic event fired when scrolling starts from touch.
        `on_scroll_move`
            Generic event fired when scrolling move from touch.
        `on_scroll_stop`
            Generic event fired when scrolling stops from touch.

    .. versionchanged:: 1.9.0
        `on_scroll_start`, `on_scroll_move` and `on_scroll_stop` events are
        now dispatched when scrolling to handle nested ScrollViews.

    .. versionchanged:: 1.7.0
        `auto_scroll`, `scroll_friction`, `scroll_moves`, `scroll_stoptime' has
        been deprecated, use :attr:`effect_cls` instead.
    '''

    scroll_distance = NumericProperty(_scroll_distance)
    '''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
    soon as the distance has been traveled, the :class:`ScrollView` will start
    to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 20 (pixels), according to the default value in user
    configuration.
    '''

    scroll_wheel_distance = NumericProperty(20)
    '''Distance to move when scrolling with a mouse wheel.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    .. versionadded:: 1.8.0

    :attr:`scroll_wheel_distance` is a
    :class:`~kivy.properties.NumericProperty` , defaults to 20 pixels.
    '''

    scroll_timeout = NumericProperty(_scroll_timeout)
    '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
    If the user has not moved :attr:`scroll_distance` within the timeout,
    the scrolling will be disabled, and the touch event will go to the
    children.

    :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 55 (milliseconds) according to the default value in user
    configuration.

    .. versionchanged:: 1.5.0
        Default value changed from 250 to 55.
    '''

    scroll_x = NumericProperty(0.)
    '''X scrolling value, between 0 and 1. If 0, the content's left side will
    touch the left side of the ScrollView. If 1, the content's right side will
    touch the right side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_x` is True.

    :attr:`scroll_x` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    '''

    scroll_y = NumericProperty(1.)
    '''Y scrolling value, between 0 and 1. If 0, the content's bottom side will
    touch the bottom side of the ScrollView. If 1, the content's top side will
    touch the top side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_y` is True.

    :attr:`scroll_y` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    '''

    do_scroll_x = BooleanProperty(True)
    '''Allow scroll on X axis.

    :attr:`do_scroll_x` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True.
    '''

    do_scroll_y = BooleanProperty(True)
    '''Allow scroll on Y axis.

    :attr:`do_scroll_y` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True.
    '''
    def _get_do_scroll(self):
        return (self.do_scroll_x, self.do_scroll_y)

    def _set_do_scroll(self, value):
        if type(value) in (list, tuple):
            self.do_scroll_x, self.do_scroll_y = value
        else:
            self.do_scroll_x = self.do_scroll_y = bool(value)

    do_scroll = AliasProperty(_get_do_scroll,
                              _set_do_scroll,
                              bind=('do_scroll_x', 'do_scroll_y'))
    '''Allow scroll on X or Y axis.

    :attr:`do_scroll` is a :class:`~kivy.properties.AliasProperty` of
    (:attr:`do_scroll_x` + :attr:`do_scroll_y`)
    '''

    def _get_vbar(self):
        # must return (y, height) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vh = self._viewport.height
        h = self.height
        if vh < h or vh == 0:
            return 0, 1.
        ph = max(0.01, h / float(vh))
        sy = min(1.0, max(0.0, self.scroll_y))
        py = (1. - ph) * sy
        return (py, ph)

    vbar = AliasProperty(_get_vbar,
                         None,
                         bind=('scroll_y', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the vertical scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little vertical bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    def _get_hbar(self):
        # must return (x, width) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vw = self._viewport.width
        w = self.width
        if vw < w or vw == 0:
            return 0, 1.
        pw = max(0.01, w / float(vw))
        sx = min(1.0, max(0.0, self.scroll_x))
        px = (1. - pw) * sx
        return (px, pw)

    hbar = AliasProperty(_get_hbar,
                         None,
                         bind=('scroll_x', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the horizontal scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little horizontal bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    bar_color = ListProperty([.7, .7, .7, .9])
    '''Color of horizontal / vertical scroll bar, in RGBA format.

    .. versionadded:: 1.2.0

    :attr:`bar_color` is a :class:`~kivy.properties.ListProperty` and defaults
    to [.7, .7, .7, .9].
    '''

    bar_inactive_color = ListProperty([.7, .7, .7, .2])
    '''Color of horizontal / vertical scroll bar (in RGBA format), when no
    scroll is happening.

    .. versionadded:: 1.9.0

    :attr:`bar_inactive_color` is a
    :class:`~kivy.properties.ListProperty` and defaults to [.7, .7, .7, .2].
    '''

    bar_width = NumericProperty('2dp')
    '''Width of the horizontal / vertical scroll bar. The width is interpreted
    as a height for the horizontal bar.

    .. versionadded:: 1.2.0

    :attr:`bar_width` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 2.
    '''

    bar_pos_x = OptionProperty('bottom', options=('top', 'bottom'))
    '''Which side of the ScrollView the horizontal scroll bar should go
    on. Possible values are 'top' and 'bottom'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_x` is an :class:`~kivy.properties.OptionProperty`,
    defaults to 'bottom'.

    '''

    bar_pos_y = OptionProperty('right', options=('left', 'right'))
    '''Which side of the ScrollView the vertical scroll bar should go
    on. Possible values are 'left' and 'right'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_y` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'right'.

    '''

    bar_pos = ReferenceListProperty(bar_pos_x, bar_pos_y)
    '''Which side of the scroll view to place each of the bars on.

    :attr:`bar_pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`bar_pos_x`, :attr:`bar_pos_y`)
    '''

    bar_margin = NumericProperty(0)
    '''Margin between the bottom / right side of the scrollview when drawing
    the horizontal / vertical scroll bar.

    .. versionadded:: 1.2.0

    :attr:`bar_margin` is a :class:`~kivy.properties.NumericProperty`, default
    to 0
    '''

    effect_cls = ObjectProperty(DampedScrollEffect, allownone=True)
    '''Class effect to instanciate for X and Y axis.

    .. versionadded:: 1.7.0

    :attr:`effect_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`DampedScrollEffect`.

    .. versionchanged:: 1.8.0
        If you set a string, the :class:`~kivy.factory.Factory` will be used to
        resolve the class.

    '''

    effect_x = ObjectProperty(None, allownone=True)
    '''Effect to apply for the X axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_x` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.
    '''

    effect_y = ObjectProperty(None, allownone=True)
    '''Effect to apply for the Y axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_y` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None, read-only.
    '''

    viewport_size = ListProperty([0, 0])
    '''(internal) Size of the internal viewport. This is the size of your only
    child in the scrollview.
    '''

    scroll_type = OptionProperty(['content'],
                                 options=(['content'], ['bars'],
                                          ['bars',
                                           'content'], ['content', 'bars']))
    '''Sets the type of scrolling to use for the content of the scrollview.
    Available options are: ['content'], ['bars'], ['bars', 'content'].

    .. versionadded:: 1.8.0

    :attr:`scroll_type` is a :class:`~kivy.properties.OptionProperty`, defaults
    to ['content'].
    '''

    # private, for internal use only

    _viewport = ObjectProperty(None, allownone=True)
    _bar_color = ListProperty([0, 0, 0, 0])

    def _set_viewport_size(self, instance, value):
        self.viewport_size = value

    def on__viewport(self, instance, value):
        if value:
            value.bind(size=self._set_viewport_size)
            self.viewport_size = value.size

    __events__ = ('on_scroll_start', 'on_scroll_move', 'on_scroll_stop')

    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

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

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)
        self.bind(width=self._update_effect_x_bounds,
                  height=self._update_effect_y_bounds,
                  viewport_size=self._update_effect_bounds,
                  _viewport=self._update_effect_widget,
                  scroll_x=self._trigger_update_from_scroll,
                  scroll_y=self._trigger_update_from_scroll,
                  pos=self._trigger_update_from_scroll,
                  size=self._trigger_update_from_scroll)

        self._update_effect_widget()
        self._update_effect_x_bounds()
        self._update_effect_y_bounds()

    def on_effect_x(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_x)
            value.target_widget = self._viewport

    def on_effect_y(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_y)
            value.target_widget = self._viewport

    def on_effect_cls(self, instance, cls):
        if isinstance(cls, string_types):
            cls = Factory.get(cls)
        self.effect_x = cls(target_widget=self._viewport)
        self.effect_x.bind(scroll=self._update_effect_x)
        self.effect_y = cls(target_widget=self._viewport)
        self.effect_y.bind(scroll=self._update_effect_y)

    def _update_effect_widget(self, *args):
        if self.effect_x:
            self.effect_x.target_widget = self._viewport
        if self.effect_y:
            self.effect_y.target_widget = self._viewport

    def _update_effect_x_bounds(self, *args):
        if not self._viewport or not self.effect_x:
            return
        self.effect_x.min = -(self.viewport_size[0] - self.width)
        self.effect_x.max = 0
        self.effect_x.value = self.effect_x.min * self.scroll_x

    def _update_effect_y_bounds(self, *args):
        if not self._viewport or not self.effect_y:
            return
        self.effect_y.min = -(self.viewport_size[1] - self.height)
        self.effect_y.max = 0
        self.effect_y.value = self.effect_y.min * self.scroll_y

    def _update_effect_bounds(self, *args):
        if not self._viewport:
            return
        if self.effect_x:
            self._update_effect_x_bounds()
        if self.effect_y:
            self._update_effect_y_bounds()

    def _update_effect_x(self, *args):
        vp = self._viewport
        if not vp or not self.effect_x:
            return
        sw = vp.width - self.width
        if sw < 1:
            return
        sx = self.effect_x.scroll / float(sw)
        self.scroll_x = -sx
        self._trigger_update_from_scroll()

    def _update_effect_y(self, *args):
        vp = self._viewport
        if not vp or not self.effect_y:
            return
        sh = vp.height - self.height
        if sh < 1:
            return
        sy = self.effect_y.scroll / float(sh)
        self.scroll_y = -sy
        self._trigger_update_from_scroll()

    def to_local(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x - tx, y - ty

    def to_parent(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x + tx, y + ty

    def _apply_transform(self, m):
        tx, ty = self.g_translate.xy
        m.translate(tx, ty, 0)
        return super(ScrollView, self)._apply_transform(m)

    def simulate_touch_down(self, touch):
        # at this point the touch is in parent coords
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(ScrollView, self).on_touch_down(touch)
        touch.pop()
        return ret

    def on_touch_down(self, touch):
        if self.dispatch('on_scroll_start', touch):
            self._touch = touch
            touch.grab(self)
            return True

    def on_scroll_start(self, touch, check_children=True):
        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_start', touch):
                return True
            touch.pop()

        if not self.collide_point(*touch.pos):
            touch.ud[self._get_uid('svavoid')] = True
            return
        if self.disabled:
            return True
        if self._touch or (not (self.do_scroll_x or self.do_scroll_y)):
            return self.simulate_touch_down(touch)

        # handle mouse scrolling, only if the viewport size is bigger than the
        # scrollview size, and if the user allowed to do it
        vp = self._viewport
        if not vp:
            return True
        scroll_type = self.scroll_type
        ud = touch.ud
        scroll_bar = 'bars' in scroll_type

        # check if touch is in bar_x(horizontal) or bay_y(bertical)
        ud['in_bar_x'] = ud['in_bar_y'] = False
        width_scrollable = vp.width > self.width
        height_scrollable = vp.height > self.height
        bar_pos_x = self.bar_pos_x[0]
        bar_pos_y = self.bar_pos_y[0]

        d = {
            'b': True if touch.y < self.y + self.bar_width else False,
            't': True if touch.y > self.top - self.bar_width else False,
            'l': True if touch.x < self.x + self.bar_width else False,
            'r': True if touch.x > self.right - self.bar_width else False
        }
        if scroll_bar:
            if (width_scrollable and d[bar_pos_x]):
                ud['in_bar_x'] = True
            if (height_scrollable and d[bar_pos_y]):
                ud['in_bar_y'] = True

        if vp and 'button' in touch.profile and \
                touch.button.startswith('scroll'):
            btn = touch.button
            m = sp(self.scroll_wheel_distance)
            e = None

            if ((btn == 'scrolldown' and self.scroll_y >= 1)
                    or (btn == 'scrollup' and self.scroll_y <= 0)
                    or (btn == 'scrollleft' and self.scroll_x >= 1)
                    or (btn == 'scrollright' and self.scroll_x <= 0)):
                return False

            if (self.effect_x and self.do_scroll_y and height_scrollable
                    and btn in ('scrolldown', 'scrollup')):
                e = self.effect_x if ud['in_bar_x'] else self.effect_y

            elif (self.effect_y and self.do_scroll_x and width_scrollable
                  and btn in ('scrollleft', 'scrollright')):
                e = self.effect_y if ud['in_bar_y'] else self.effect_x

            if e:
                if btn in ('scrolldown', 'scrollleft'):
                    e.value = max(e.value - m, e.min)
                    e.velocity = 0
                elif btn in ('scrollup', 'scrollright'):
                    e.value = min(e.value + m, e.max)
                    e.velocity = 0
                touch.ud[self._get_uid('svavoid')] = True
                e.trigger_velocity_update()
            return True

        # no mouse scrolling, so the user is going to drag the scrollview with
        # this touch.
        self._touch = touch
        uid = self._get_uid()

        ud[uid] = {
            'mode': 'unknown',
            'dx': 0,
            'dy': 0,
            'user_stopped': False,
            'frames': Clock.frames,
            'time': touch.time_start
        }

        if self.do_scroll_x and self.effect_x and not ud['in_bar_x']:
            self.effect_x.start(touch.x)
            self._scroll_x_mouse = self.scroll_x
        if self.do_scroll_y and self.effect_y and not ud['in_bar_y']:
            self.effect_y.start(touch.y)
            self._scroll_y_mouse = self.scroll_y

        if (ud.get('in_bar_x', False) or ud.get('in_bar_y', False)):
            return True

        Clock.schedule_once(self._change_touch_mode,
                            self.scroll_timeout / 1000.)
        if scroll_type == ['bars']:
            return False
        else:
            return True

    def on_touch_move(self, touch):
        if self._touch is not touch:
            # touch is in parent
            touch.push()
            touch.apply_transform_2d(self.to_local)
            super(ScrollView, self).on_touch_move(touch)
            touch.pop()
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True

        if not (self.do_scroll_y or self.do_scroll_x):
            return super(ScrollView, self).on_touch_move(touch)

        touch.ud['sv.handled'] = {'x': False, 'y': False}
        if self.dispatch('on_scroll_move', touch):
            return True

    def on_scroll_move(self, touch):
        if self._get_uid('svavoid') in touch.ud:
            return False

        touch.push()
        touch.apply_transform_2d(self.to_local)
        if self.dispatch_children('on_scroll_move', touch):
            return True
        touch.pop()

        rv = True

        uid = self._get_uid()
        if not uid in touch.ud:
            self._touch = False
            return self.on_scroll_start(touch, False)
        ud = touch.ud[uid]
        mode = ud['mode']

        # check if the minimum distance has been travelled
        if mode == 'unknown' or mode == 'scroll':
            if not touch.ud['sv.handled']['x'] and self.do_scroll_x \
                    and self.effect_x:
                width = self.width
                if touch.ud.get('in_bar_x', False):
                    dx = touch.dx / float(width - width * self.hbar[1])
                    self.scroll_x = min(max(self.scroll_x + dx, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_x.update(touch.x)
                if self.scroll_x < 0 or self.scroll_x > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['x'] = True
            if not touch.ud['sv.handled']['y'] and self.do_scroll_y \
                    and self.effect_y:
                height = self.height
                if touch.ud.get('in_bar_y', False):
                    dy = touch.dy / float(height - height * self.vbar[1])
                    self.scroll_y = min(max(self.scroll_y + dy, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_y.update(touch.y)
                if self.scroll_y < 0 or self.scroll_y > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['y'] = True

        if mode == 'unknown':
            ud['dx'] += abs(touch.dx)
            ud['dy'] += abs(touch.dy)
            if ud['dx'] > self.scroll_distance:
                if not self.do_scroll_x:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'

            if ud['dy'] > self.scroll_distance:
                if not self.do_scroll_y:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'
            ud['mode'] = mode

        if mode == 'scroll':
            ud['dt'] = touch.time_update - ud['time']
            ud['time'] = touch.time_update
            ud['user_stopped'] = True

        return rv

    def on_touch_up(self, touch):
        if self._touch is not touch and self.uid not in touch.ud:
            # touch is in parents
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if super(ScrollView, self).on_touch_up(touch):
                return True
            touch.pop()
            return False

        if self.dispatch('on_scroll_stop', touch):
            touch.ungrab(self)
            return True

    def on_scroll_stop(self, touch, check_children=True):
        self._touch = None

        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_stop', touch):
                return True
            touch.pop()

        if self._get_uid('svavoid') in touch.ud:
            return
        if self._get_uid() not in touch.ud:
            return False

        self._touch = None
        uid = self._get_uid()
        ud = touch.ud[uid]
        if self.do_scroll_x and self.effect_x:
            if not touch.ud.get('in_bar_x', False) and\
                    self.scroll_type != ['bars']:
                self.effect_x.stop(touch.x)
        if self.do_scroll_y and self.effect_y and\
                self.scroll_type != ['bars']:
            if not touch.ud.get('in_bar_y', False):
                self.effect_y.stop(touch.y)
        if ud['mode'] == 'unknown':
            # we must do the click at least..
            # only send the click if it was not a click to stop
            # autoscrolling
            if not ud['user_stopped']:
                self.simulate_touch_down(touch)
            Clock.schedule_once(partial(self._do_touch_up, touch), .2)
        Clock.unschedule(self._update_effect_bounds)
        Clock.schedule_once(self._update_effect_bounds)

        # if we do mouse scrolling, always accept it
        if 'button' in touch.profile and touch.button.startswith('scroll'):
            return True

        return self._get_uid() in touch.ud

    def convert_distance_to_scroll(self, dx, dy):
        '''Convert a distance in pixels to a scroll distance, depending on the
        content size and the scrollview size.

        The result will be a tuple of scroll distance that can be added to
        :data:`scroll_x` and :data:`scroll_y`
        '''
        if not self._viewport:
            return 0, 0
        vp = self._viewport
        if vp.width > self.width:
            sw = vp.width - self.width
            sx = dx / float(sw)
        else:
            sx = 0
        if vp.height > self.height:
            sh = vp.height - self.height
            sy = dy / float(sh)
        else:
            sy = 1
        return sx, sy

    def update_from_scroll(self, *largs):
        '''Force the reposition of the content, according to current value of
        :attr:`scroll_x` and :attr:`scroll_y`.

        This method is automatically called when one of the :attr:`scroll_x`,
        :attr:`scroll_y`, :attr:`pos` or :attr:`size` properties change, or
        if the size of the content changes.
        '''
        if not self._viewport:
            return
        vp = self._viewport

        # update from size_hint
        if vp.size_hint_x is not None:
            vp.width = vp.size_hint_x * self.width
        if vp.size_hint_y is not None:
            vp.height = vp.size_hint_y * self.height

        if vp.width > self.width:
            sw = vp.width - self.width
            x = self.x - self.scroll_x * sw
        else:
            x = self.x
        if vp.height > self.height:
            sh = vp.height - self.height
            y = self.y - self.scroll_y * sh
        else:
            y = self.top - vp.height

        # from 1.8.0, we now use a matrix by default, instead of moving the
        # widget position behind. We set it here, but it will be a no-op most of
        # the time.
        vp.pos = 0, 0
        self.g_translate.xy = x, y

        # New in 1.2.0, show bar when scrolling happens and (changed in 1.9.0)
        # fade to bar_inactive_color when no scroll is happening.
        Clock.unschedule(self._bind_inactive_bar_color)
        self.unbind(bar_inactive_color=self._change_bar_color)
        Animation.stop_all(self, '_bar_color')
        self.bind(bar_color=self._change_bar_color)
        self._bar_color = self.bar_color
        Clock.schedule_once(self._bind_inactive_bar_color, .5)

    def _bind_inactive_bar_color(self, *l):
        self.unbind(bar_color=self._change_bar_color)
        self.bind(bar_inactive_color=self._change_bar_color)
        Animation(_bar_color=self.bar_inactive_color, d=.5,
                  t='out_quart').start(self)

    def _change_bar_color(self, inst, value):
        self._bar_color = value

    #
    # Private
    #
    def add_widget(self, widget, index=0):
        if self._viewport:
            raise Exception('ScrollView accept only one widget')
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).add_widget(widget, index)
        self.canvas = canvas
        self._viewport = widget
        widget.bind(size=self._trigger_update_from_scroll)
        self._trigger_update_from_scroll()

    def remove_widget(self, widget):
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).remove_widget(widget)
        self.canvas = canvas
        if widget is self._viewport:
            self._viewport = None

    def _get_uid(self, prefix='sv'):
        return '{0}.{1}'.format(prefix, self.uid)

    def _change_touch_mode(self, *largs):
        if not self._touch:
            return
        uid = self._get_uid()
        touch = self._touch
        if uid not in touch.ud:
            self._touch = False
            return
        ud = touch.ud[uid]
        if ud['mode'] != 'unknown' or ud['user_stopped']:
            return
        diff_frames = Clock.frames - ud['frames']

        # in order to be able to scroll on very slow devices, let at least 3
        # frames displayed to accumulate some velocity. And then, change the
        # touch mode. Otherwise, we might never be able to compute velocity, and
        # no way to scroll it. See #1464 and #1499
        if diff_frames < 3:
            Clock.schedule_once(self._change_touch_mode, 0)
            return

        if self.do_scroll_x and self.effect_x:
            self.effect_x.cancel()
        if self.do_scroll_y and self.effect_y:
            self.effect_y.cancel()
        # XXX the next line was in the condition. But this stop
        # the possibily to "drag" an object out of the scrollview in the
        # non-used direction: if you have an horizontal scrollview, a
        # vertical gesture will not "stop" the scroll view to look for an
        # horizontal gesture, until the timeout is done.
        # and touch.dx + touch.dy == 0:
        touch.ungrab(self)
        self._touch = None
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        touch.apply_transform_2d(self.to_parent)
        self.simulate_touch_down(touch)
        touch.pop()
        return

    def _do_touch_up(self, touch, *largs):
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        super(ScrollView, self).on_touch_up(touch)
        touch.pop()
        # don't forget about grab event!
        for x in touch.grab_list[:]:
            touch.grab_list.remove(x)
            x = x()
            if not x:
                continue
            touch.grab_current = x
            # touch is in window coords
            touch.push()
            touch.apply_transform_2d(self.to_widget)
            super(ScrollView, self).on_touch_up(touch)
            touch.pop()
        touch.grab_current = None
Beispiel #19
0
class TabbedPanel(GridLayout):
    '''The TabbedPanel class. See module documentation for more information.
    '''

    background_color = ListProperty([1, 1, 1, 1])
    '''Background color, in the format (r, g, b, a).

    :data:`background_color` is a :class:`~kivy.properties.ListProperty`,
    default to [1, 1, 1, 1].
    '''

    border = ListProperty([16, 16, 16, 16])
    '''Border used for :class:`~kivy.graphics.vertex_instructions.BorderImage`
    graphics instruction, used itself for :data:`background_image`.
    Can be changed for a custom background.

    It must be a list of four values: (top, right, bottom, left). Read the
    BorderImage instructions for more information.

    :data:`border` is a :class:`~kivy.properties.ListProperty`,
    default to (16, 16, 16, 16)
    '''

    background_image = StringProperty('atlas://data/images/defaulttheme/tab')
    '''Background image of the main shared content object.

    :data:`background_image` is a :class:`~kivy.properties.StringProperty`,
    default to 'atlas://data/images/defaulttheme/tab'.
    '''

    _current_tab = ObjectProperty(None)

    def get_current_tab(self):
        return self._current_tab

    current_tab = AliasProperty(get_current_tab, None, bind=('_current_tab', ))
    '''Links to the currently select or active tab.

    .. versionadded:: 1.4.0

    :data:`current_tab` is a :class:`~kivy.AliasProperty`, read-only.
    '''

    tab_pos = OptionProperty(
        'top_left',
        options=('left_top', 'left_mid', 'left_bottom', 'top_left', 'top_mid',
                 'top_right', 'right_top', 'right_mid', 'right_bottom',
                 'bottom_left', 'bottom_mid', 'bottom_right'))
    '''Specifies the position of the tabs relative to the content.
    Can be one of: `left_top`, `left_mid`, `left_bottom`, `top_left`,
    `top_mid`, `top_right`, `right_top`, `right_mid`, `right_bottom`,
    `bottom_left`, `bottom_mid`, `bottom_right`.

    :data:`tab_pos` is a :class:`~kivy.properties.OptionProperty`,
    default to 'bottom_mid'.
    '''

    tab_height = NumericProperty('40dp')
    '''Specifies the height of the tab header.

    :data:`tab_height` is a :class:`~kivy.properties.NumericProperty`,
    default to 40.
    '''

    tab_width = NumericProperty('100dp', allownone=True)
    '''Specifies the width of the tab header.

    :data:`tab_width` is a :class:`~kivy.properties.NumericProperty`,
    default to 100.
    '''

    do_default_tab = BooleanProperty(True)
    '''Specifies weather a default_tab head is provided.

    .. versionadded:: 1.5.0

    :data:`do_default_tab` is a :class:`~kivy.properties.BooleanProperty`,
    defaults to 'True'.
    '''

    default_tab_text = StringProperty('Default tab')
    '''Specifies the text displayed on the default tab header.

    :data:`default_tab_text` is a :class:`~kivy.properties.StringProperty`,
    defaults to 'default tab'.
    '''

    default_tab_cls = ObjectProperty(TabbedPanelHeader)
    '''Specifies the class to use for the styling of the default tab.

    .. versionadded:: 1.4.0

    .. warning::
        `default_tab_cls` should be subclassed from `TabbedPanelHeader`

    :data:`default_tab_cls` is a :class:`~kivy.properties.ObjectProperty`,
    default to `TabbedPanelHeader`.
    '''

    def get_tab_list(self):
        if self._tab_strip:
            return self._tab_strip.children
        return 1.

    tab_list = AliasProperty(get_tab_list, None)
    '''List of all the tab headers.

    :data:`tab_list` is a :class:`~kivy.properties.AliasProperty`, and is
    read-only.
    '''

    content = ObjectProperty(None)
    '''This is the object holding the content of the current tab.

    :data:`content` is a :class:`~kivy.properties.ObjectProperty`,
    default to 'None'.
    '''

    def get_def_tab(self):
        return self._default_tab

    def set_def_tab(self, new_tab):
        if not issubclass(new_tab.__class__, TabbedPanelHeader):
            raise TabbedPanelException('`default_tab_class` should be\
                subclassed from `TabbedPanelHeader`')
        if self._default_tab == new_tab:
            return
        oltab = self._default_tab
        self._default_tab = new_tab
        if hasattr(self, '_original_tab') and self._original_tab == oltab:
            self.remove_widget(oltab)
            self._original_tab = None
        self.switch_to(new_tab)
        new_tab.state = 'down'

    default_tab = AliasProperty(get_def_tab, set_def_tab)
    '''Holds the default tab.

    .. Note:: For convenience, the automatically provided default tab is
              deleted when you change default_tab to something else.

    :data:`default_tab` is a :class:`~kivy.properties.AliasProperty`
    '''

    def get_def_tab_content(self):
        return self.default_tab.content

    def set_def_tab_content(self, *l):
        self.default_tab.content = l[0]

    default_tab_content = AliasProperty(get_def_tab_content,
                                        set_def_tab_content)
    '''Holds the default tab content.

    :data:`default_tab_content` is a :class:`~kivy.properties.AliasProperty`
    '''

    def __init__(self, **kwargs):
        # these variables need to be initialised before the kv lang is
        # processed setup the base layout for the tabbed panel
        self._tab_layout = GridLayout(rows=1)
        self.rows = 1
        # bakground_image
        self._bk_img = Image(source=self.background_image,
                             allow_stretch=True,
                             keep_ratio=False,
                             color=self.background_color)

        self._tab_strip = TabbedPanelStrip(tabbed_panel=self,
                                           rows=1,
                                           cols=99,
                                           size_hint=(None, None),
                                           height=self.tab_height,
                                           width=self.tab_width)

        self._partial_update_scrollview = None
        self.content = TabbedPanelContent()
        self._current_tab = self._original_tab \
            = self._default_tab = TabbedPanelHeader()

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

        self.bind(size=self._reposition_tabs)
        if not self.do_default_tab:
            Clock.schedule_once(self._switch_to_first_tab)
            return
        self._setup_default_tab()
        self.switch_to(self.default_tab)

    def switch_to(self, header):
        '''Switch to a specific panel header.
        '''
        header_content = header.content
        self._current_tab.state = 'normal'
        header.state = 'down'
        self._current_tab = header
        self.clear_widgets()
        if header_content is None:
            return
        # if content has a previous parent remove it from that parent
        parent = header_content.parent
        if parent:
            parent.remove_widget(header_content)
        self.add_widget(header_content)

    def clear_tabs(self, *l):
        self_tabs = self._tab_strip
        self_tabs.clear_widgets()
        if self.do_default_tab:
            self_default_tab = self._default_tab
            self_tabs.add_widget(self_default_tab)
            self_tabs.width = self_default_tab.width
        self._reposition_tabs()

    def add_widget(self, widget, index=0):
        content = self.content
        if content is None:
            return
        parent = widget.parent
        if parent:
            parent.remove_widget(widget)
        if widget in (content, self._tab_layout):
            super(TabbedPanel, self).add_widget(widget, index)
        elif isinstance(widget, TabbedPanelHeader):
            self_tabs = self._tab_strip
            self_tabs.add_widget(widget)
            widget.group = '__tab%r__' % self_tabs.uid
            self.on_tab_width()
        else:
            widget.pos_hint = {'x': 0, 'top': 1}
            content.add_widget(widget, index)

    def remove_widget(self, widget):
        content = self.content
        if content is None:
            return
        if widget in (content, self._tab_layout):
            super(TabbedPanel, self).remove_widget(widget)
        elif isinstance(widget, TabbedPanelHeader):
            if widget != self._default_tab:
                self_tabs = self._tab_strip
                self_tabs.width -= widget.width
                self_tabs.remove_widget(widget)
                if widget.state == 'down':
                    if self.do_default_tab:
                        self._default_tab.on_release()
                self._reposition_tabs()
            else:
                Logger.info('TabbedPanel: default tab! can\'t be removed.\n' +
                            'Change `default_tab` to a different tab.')
        else:
            if widget in content.children:
                content.remove_widget(widget)

    def clear_widgets(self, **kwargs):
        content = self.content
        if content is None:
            return
        if kwargs.get('do_super', False):
            super(TabbedPanel, self).clear_widgets()
        else:
            content.clear_widgets()

    def on_background_image(self, *l):
        self._bk_img.source = self.background_image

    def on_background_color(self, *l):
        if self.content is None:
            return
        self._bk_img.color = self.background_color

    def on_do_default_tab(self, instance, value):
        if not value:
            dft = self.default_tab
            if dft in self.tab_list:
                self._default_tab = None
                self.remove_widget(dft)
                self._switch_to_first_tab()
        else:
            self._current_tab.state = 'normal'
            self._setup_default_tab()

    def on_default_tab_text(self, *args):
        self._default_tab.text = self.default_tab_text

    def on_tab_width(self, *l):
        Clock.unschedule(self._update_tab_width)
        Clock.schedule_once(self._update_tab_width, 0)

    def on_tab_height(self, *l):
        self._tab_layout.height = self._tab_strip.height = self.tab_height
        self._reposition_tabs()

    def on_tab_pos(self, *l):
        # ensure canvas
        self._reposition_tabs()

    def _setup_default_tab(self):
        if self._default_tab in self.tab_list:
            return
        content = self._default_tab.content
        _tabs = self._tab_strip
        cls = self.default_tab_cls

        if not issubclass(cls, TabbedPanelHeader):
            raise TabbedPanelException('`default_tab_class` should be\
                subclassed from `TabbedPanelHeader`')

        # no need to instanciate if class is TabbedPanelHeader
        if cls != TabbedPanelHeader:
            self._current_tab = self._original_tab = self._default_tab = cls()

        default_tab = self.default_tab
        if self._original_tab == self.default_tab:
            default_tab.text = self.default_tab_text

        default_tab.height = self.tab_height
        default_tab.group = '__tab%r__' % _tabs.uid
        default_tab.state = 'down'
        default_tab.width = self.tab_width if self.tab_width else 100
        default_tab.content = content

        tl = self.tab_list
        if default_tab not in tl:
            _tabs.add_widget(default_tab, len(tl))

        if default_tab.content:
            self.clear_widgets()
            self.add_widget(self.default_tab.content)
        else:
            Clock.schedule_once(self._load_default_tab_content)
        self._current_tab = default_tab

    def _switch_to_first_tab(self, *l):
        ltl = len(self.tab_list) - 1
        if ltl > -1:
            self._current_tab = dt = self._original_tab \
                = self.tab_list[ltl]
            self.switch_to(dt)

    def _load_default_tab_content(self, dt):
        if self.default_tab:
            self.switch_to(self.default_tab)

    def _reposition_tabs(self, *l):
        Clock.unschedule(self._update_tabs)
        Clock.schedule_once(self._update_tabs, 0)

    def _update_tabs(self, *l):
        self_content = self.content
        if not self_content:
            return
        # cache variables for faster access
        tab_pos = self.tab_pos
        tab_layout = self._tab_layout
        tab_layout.clear_widgets()
        scrl_v = ScrollView(size_hint=(None, 1))
        tabs = self._tab_strip
        parent = tabs.parent
        if parent:
            parent.remove_widget(tabs)
        scrl_v.add_widget(tabs)
        scrl_v.pos = (0, 0)
        self_update_scrollview = self._update_scrollview

        # update scrlv width when tab width changes depends on tab_pos
        if self._partial_update_scrollview is not None:
            tabs.unbind(width=self._partial_update_scrollview)
        self._partial_update_scrollview = partial(self_update_scrollview,
                                                  scrl_v)
        tabs.bind(width=self._partial_update_scrollview)

        # remove all widgets from the tab_strip
        self.clear_widgets(do_super=True)
        tab_height = self.tab_height

        widget_list = []
        tab_list = []
        pos_letter = tab_pos[0]
        if pos_letter == 'b' or pos_letter == 't':
            # bottom or top positions
            # one col containing the tab_strip and the content
            self.cols = 1
            self.rows = 2
            # tab_layout contains the scrollview containing tabs and two blank
            # dummy widgets for spacing
            tab_layout.rows = 1
            tab_layout.cols = 3
            tab_layout.size_hint = (1, None)
            tab_layout.height = tab_height
            self_update_scrollview(scrl_v)

            if pos_letter == 'b':
                # bottom
                if tab_pos == 'bottom_mid':
                    tab_list = (Widget(), scrl_v, Widget())
                    widget_list = (self_content, tab_layout)
                else:
                    if tab_pos == 'bottom_left':
                        tab_list = (scrl_v, Widget(), Widget())
                    elif tab_pos == 'bottom_right':
                        #add two dummy widgets
                        tab_list = (Widget(), Widget(), scrl_v)
                    widget_list = (self_content, tab_layout)
            else:
                # top
                if tab_pos == 'top_mid':
                    tab_list = (Widget(), scrl_v, Widget())
                elif tab_pos == 'top_left':
                    tab_list = (scrl_v, Widget(), Widget())
                elif tab_pos == 'top_right':
                    tab_list = (Widget(), Widget(), scrl_v)
                widget_list = (tab_layout, self_content)
        elif pos_letter == 'l' or pos_letter == 'r':
            # left ot right positions
            # one row containing the tab_strip and the content
            self.cols = 2
            self.rows = 1
            # tab_layout contains two blank dummy widgets for spacing
            # "vertically" and the scatter containing scrollview
            # containing tabs
            tab_layout.rows = 3
            tab_layout.cols = 1
            tab_layout.size_hint = (None, 1)
            tab_layout.width = tab_height
            scrl_v.height = tab_height
            self_update_scrollview(scrl_v)

            # rotate the scatter for vertical positions
            rotation = 90 if tab_pos[0] == 'l' else -90
            sctr = Scatter(do_translation=False,
                           rotation=rotation,
                           do_rotation=False,
                           do_scale=False,
                           size_hint=(None, None),
                           auto_bring_to_front=False,
                           size=scrl_v.size)
            sctr.add_widget(scrl_v)

            lentab_pos = len(tab_pos)

            # Update scatter's top when it's pos changes.
            # Needed for repositioning scatter to the correct place after its
            # added to the parent. Use clock_schedule_once to ensure top is
            # calculated after the parent's pos on canvas has been calculated.
            # This is needed for when tab_pos changes to correctly position
            # scatter. Without clock.schedule_once the positions would look
            # fine but touch won't translate to the correct position

            if tab_pos[lentab_pos - 4:] == '_top':
                #on positions 'left_top' and 'right_top'
                sctr.bind(pos=partial(self._update_top, sctr, 'top', None))
                tab_list = (sctr, )
            elif tab_pos[lentab_pos - 4:] == '_mid':
                #calculate top of scatter
                sctr.bind(
                    pos=partial(self._update_top, sctr, 'mid', scrl_v.width))
                tab_list = (Widget(), sctr, Widget())
            elif tab_pos[lentab_pos - 7:] == '_bottom':
                tab_list = (Widget(), Widget(), sctr)

            if pos_letter == 'l':
                widget_list = (tab_layout, self_content)
            else:
                widget_list = (self_content, tab_layout)

        # add widgets to tab_layout
        add = tab_layout.add_widget
        for widg in tab_list:
            add(widg)

        # add widgets to self
        add = self.add_widget
        for widg in widget_list:
            add(widg)

    def _update_tab_width(self, *l):
        if self.tab_width:
            for tab in self.tab_list:
                tab.size_hint_x = 1
            tsw = self.tab_width * len(self._tab_strip.children)
        else:
            # tab_width = None
            tsw = 0
            for tab in self.tab_list:
                if tab.size_hint_x:
                    # size_hint_x: x/.xyz
                    tab.size_hint_x = 1
                    #drop to default tab_width
                    tsw += 100
                else:
                    # size_hint_x: None
                    tsw += tab.width
        self._tab_strip.width = tsw
        self._reposition_tabs()

    def _update_top(self, *args):
        sctr, top, scrl_v_width, x, y = args
        Clock.unschedule(partial(self._updt_top, sctr, top, scrl_v_width))
        Clock.schedule_once(partial(self._updt_top, sctr, top, scrl_v_width),
                            0)

    def _updt_top(self, sctr, top, scrl_v_width, *args):
        if top[0] == 't':
            sctr.top = self.top
        else:
            sctr.top = self.top - (self.height - scrl_v_width) / 2

    def _update_scrollview(self, scrl_v, *l):
        self_tab_pos = self.tab_pos
        self_tabs = self._tab_strip
        if self_tab_pos[0] == 'b' or self_tab_pos[0] == 't':
            #bottom or top
            scrl_v.width = min(self.width, self_tabs.width)
            #required for situations when scrl_v's pos is calculated
            #when it has no parent
            scrl_v.top += 1
            scrl_v.top -= 1
        else:
            # left or right
            scrl_v.width = min(self.height, self_tabs.width)
            self_tabs.pos = (0, 0)
Beispiel #20
0
class NaviProgram(BoxLayout):
    '''
	The application area that shows the user's program.
	Defines the structure of the program as a ListProperty.
	'''
    program = ListProperty([])
    variable_dict = DictProperty({})
    function_dict = DictProperty({})

    def print_var_dict(self):
        var_dict = ''
        for variable in self.variable_dict:
            var_dict += '%s : %r\n' % (variable, self.variable_dict[variable])
        return var_dict

    def add_statement(self, statement):
        '''
		Function that takes a statement and adds it to the program ListProperty.
		Also adds a Button widget to show the statement exists in the NaviProgram area.
		'''
        # TODO: tidy this abomination up...
        #print(self.program)
        add = True
        if statement.split()[0] != 'Comment:':
            #print(statement.split())
            if statement.split()[0] == 'TURN_A' or statement.split(
            )[0] == 'TURN_C':
                try:
                    int(statement.split()[1])
                    self.program.append(statement)
                except:
                    try:
                        self.variable_dict[statement.split()[1]]
                        self.program.append(statement)
                    except KeyError:
                        print('Not found in variable dictionary')
                        add = False
            else:
                self.program.append(statement)

        if statement.split(',')[0] == 'HEAD_LIST':
            self.eval_head_list()
        if statement.split(',')[0] == 'TAIL_LIST':
            self.eval_tail_list()
        if statement.split(',')[0] == 'SET_L':
            # TODO type validation
            self.variable_dict[statement.split(',')[1]] = statement.split(
                ',')[2]
        if statement.split(',')[0] == 'BUILD_LIST':
            # TODO type validation
            self.variable_dict[statement.split(',')[1]] = str(
                statement.split(',')[2:])
        if len(self.program) >= 2:
            if self.program[-2].split(',')[0] == 'SET_TO' and len(
                    self.program[-2].split(',')) != 3:
                self.merge_set_to()
        if statement == 'ENDIF':
            self.merge_if_statement()
        if statement == 'ENDCOND':
            self.merge_conditional()
        if statement == 'ENDFUNCTION':
            self.merge_function()

        if add:
            self.add_widget(Button(text=statement))

        print('Program: ', self.program)
        print(self.variable_dict)

    def eval_head_list(self):
        tmp_head = self.program[-1].split(',')
        head_statement = '%s[0]' % tmp_head[1]
        self.program = self.program[:-1]
        self.program.append(head_statement)

    def eval_tail_list(self):
        tmp_tail = self.program[-1].split(',')
        tail_statement = '%s[1:]' % tmp_tail[1]
        self.program = self.program[:-1]
        self.program.append(tail_statement)

    def merge_set_to(self):
        # guarenteed to be the last two elements
        # merge into form
        # SET_TO,variable,result
        set_to = 'SET_TO,%s,%s' % (self.program[-2].split(',')[1],
                                   (self.program[-1]))
        self.variable_dict[self.program[-2].split(',')[1]] = self.program[
            -1]  # add to variable dictionary
        self.program = self.program[:-2]
        self.program.append(set_to)  # append to the program

    def merge_function(self):
        # merge into form
        # start:
        # 'FUNCTION', 'f1 n', 'MOVE', 'ENDFUNCTION'
        # end:
        # 'FUNCTION (name) (args) (statement),(statement) ENDFUNCTION'

        start_funct_index = self.program.index('FUNCTION')
        end_funct_index = self.program.index('ENDFUNCTION')

        temp_funct = self.program[start_funct_index:]
        self.program = self.program[:start_funct_index]
        funct = ''
        for s in temp_funct:
            if s == 'FUNCTION':
                funct += '%s ' % s
            elif s == 'ENDFUNCTION':
                # remove the last comma
                funct = funct[:-1]
                funct += ' %s' % s
            elif len(s.split()) > 1:
                funct += '%s ' % s
            else:
                funct += '%s,' % s
        self.program.append(funct)

    def merge_conditional(self):
        # merge into form:
        # "COND (comparator)(boolean)(comparator) ENDCOND"
        start_cond_index = self.program.index('COND')
        end_cond_index = self.program.index('ENDCOND')

        temp_cond = self.program[start_cond_index:]
        self.program = self.program[:start_cond_index]
        cond = ''
        for s in temp_cond:
            if s == 'COND':
                cond += 'COND '
            elif s == 'ENDCOND':
                cond += ' ENDCOND'
            elif s == 'not':
                cond += ' %s' % s
            else:
                cond += '%s' % s
        self.program.append(cond)

    def merge_if_statement(self):
        # merge into the form:
        # IF,(condition),statement,statement,ENDIF
        start_if_index = self.program.index('IF')
        end_if_index = self.program.index('ENDIF')
        temp_if = self.program[start_if_index:]
        self.program = self.program[:start_if_index]
        if_statement = ''
        for s in temp_if:
            if_statement += '%s,' % s
        if_statement = if_statement[:-1]  # remove the last comma
        self.program.append(if_statement)

    def reset(self):
        self.program = []
        self.variable_dict = {}
        self.clear_widgets()
Beispiel #21
0
class my_MDToolbar(
    ThemableBehavior,
    RectangularElevationBehavior,
    SpecificBackgroundColorBehavior,
    BoxLayout,
):
    """
    :Events:
        `on_action_button`
            Method for the button used for the :class:`~MDBottomAppBar` class.
    """

    left_action_items = ListProperty()
    """The icons on the left of the toolbar.
    To add one, append a list like the following:

    .. code-block:: kv

        left_action_items: [`'icon_name'`, callback]

    where `'icon_name'` is a string that corresponds to an icon definition and
    ``callback`` is the function called on a touch release event.

    :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    right_action_items = ListProperty()
    """The icons on the left of the toolbar.
    Works the same way as :attr:`left_action_items`.

    :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    title = StringProperty()
    """Text toolbar.

    :attr:`title` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    md_bg_color = ListProperty([0, 0, 0, 0])
    """Color toolbar.
    
    :attr:`md_bg_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[0, 0, 0, 0]`.
    """

    anchor_title = StringProperty("left")

    mode = OptionProperty(
        "center", options=["free-end", "free-center", "end", "center"]
    )
    """Floating button position. Onle for :class:`~MDBottomAppBar` class.
    Available options are: `'free-end'`, `'free-center'`, `'end'`, `'center'`.

    :attr:`mode` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'center'`.
    """

    round = NumericProperty("10dp")
    """
    Rounding the corners at the notch for a button.
    Onle for :class:`~MDBottomAppBar` class.

    :attr:`round` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `'10dp'`.
    """

    icon = StringProperty("android")
    """
    Floating button. Onle for :class:`~MDBottomAppBar` class.

    :attr:`icon` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'android'`.
    """

    icon_color = ListProperty()
    """
    Color action button. Onle for :class:`~MDBottomAppBar` class.

    :attr:`icon_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    type = OptionProperty("top", options=["top", "bottom"])
    """
    When using the :class:`~MDBottomAppBar` class, the parameter ``type``
    must be set to `'bottom'`:

    .. code-block:: kv

        MDBottomAppBar:

            MDToolbar:
                type: "bottom"
    
    Available options are: `'top'`, `'bottom'`.

    :attr:`type` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'top'`.
    """

    _shift = NumericProperty("3.5dp")
    _angle_start = NumericProperty(90)
    _angle_end = NumericProperty(270)

    def __init__(self, **kwargs):
        self.action_button = MDActionBottomAppBarButton()
        super().__init__(**kwargs)
        self.register_event_type("on_action_button")
        self.action_button.bind(
            on_release=lambda x: self.dispatch("on_action_button")
        )
        self.action_button.x = Window.width / 2 - self.action_button.width / 2
        self.action_button.y = (
            (self.center[1] - self.height / 2)
            + self.theme_cls.standard_increment / 2
            + self._shift
        )
        if not self.icon_color:
            self.icon_color = self.theme_cls.primary_color
        Window.bind(on_resize=self._on_resize)
        self.bind(specific_text_color=self.update_action_bar_text_colors)
        Clock.schedule_once(
            lambda x: self.on_left_action_items(0, self.left_action_items)
        )
        Clock.schedule_once(
            lambda x: self.on_right_action_items(0, self.right_action_items)
        )

    def on_action_button(self, *args):
        pass

    def on_md_bg_color(self, instance, value):
        if self.type == "bottom":
            self.md_bg_color = [0, 0, 0, 0]

    def on_left_action_items(self, instance, value):
        self.update_action_bar(self.ids["left_actions"], value)

    def on_right_action_items(self, instance, value):
        self.update_action_bar(self.ids["right_actions"], value)

    def update_action_bar(self, action_bar, action_bar_items):
        action_bar.clear_widgets()
        new_width = 0
        for item in action_bar_items:
            new_width += dp(48)
            action_bar.add_widget(
                MDIconButton(
                    icon=item[0],
                    on_release=item[1],
                    opposite_colors=True,
                    text_color=self.specific_text_color,
                    ################################3333333333333333333
                    theme_text_color="ContrastParentBackground",#Custom
                )
            )
        action_bar.width = new_width

    def update_action_bar_text_colors(self, instance, value):
        for child in self.ids["left_actions"].children:
            child.text_color = self.specific_text_color
        for child in self.ids["right_actions"].children:
            child.text_color = self.specific_text_color

    def _on_resize(self, instance, width, height):
        if self.mode == "center":
            self.action_button.x = width / 2 - self.action_button.width / 2
        else:
            self.action_button.x = width - self.action_button.width * 2

    def on_icon(self, instance, value):
        self.action_button.icon = value

    def on_icon_color(self, instance, value):
        self.action_button.md_bg_color = value

    def on_mode(self, instance, value):
        def set_button_pos(*args):
            self.action_button.x = x
            self.action_button.y = y
            self.action_button._hard_shadow_size = (0, 0)
            self.action_button._soft_shadow_size = (0, 0)
            anim = Animation(_scale_x=1, _scale_y=1, d=0.05)
            anim.bind(on_complete=self.set_shadow)
            anim.start(self.action_button)

        if value == "center":
            self.set_notch()
            x = Window.width / 2 - self.action_button.width / 2
            y = (
                (self.center[1] - self.height / 2)
                + self.theme_cls.standard_increment / 2
                + self._shift
            )
        elif value == "end":

            self.set_notch()
            x = Window.width - self.action_button.width * 2
            y = (
                (self.center[1] - self.height / 2)
                + self.theme_cls.standard_increment / 2
                + self._shift
            )
            self.right_action_items = []
        elif value == "free-end":
            self.remove_notch()
            x = Window.width - self.action_button.width - dp(10)
            y = self.action_button.height + self.action_button.height / 2
        elif value == "free-center":
            self.remove_notch()
            x = Window.width / 2 - self.action_button.width / 2
            y = self.action_button.height + self.action_button.height / 2
        self.remove_shadow()
        anim = Animation(_scale_x=0, _scale_y=0, d=0.05)
        anim.bind(on_complete=set_button_pos)
        anim.start(self.action_button)

    def remove_notch(self):
        self._angle_start = 0
        self._angle_end = 0
        self.round = 0
        self._shift = 0

    def set_notch(self):
        self._angle_start = 90
        self._angle_end = 270
        self.round = dp(10)
        self._shift = dp(3.5)

    def remove_shadow(self):
        self.action_button._hard_shadow_size = (0, 0)
        self.action_button._soft_shadow_size = (0, 0)

    def set_shadow(self, *args):
        self.action_button._hard_shadow_size = (dp(112), dp(112))
        self.action_button._soft_shadow_size = (dp(112), dp(112))
Beispiel #22
0
class UserAnimationCard(ThemableBehavior, FloatLayout):
    user_name = StringProperty()
    path_to_avatar = StringProperty()
    _callback_back = ObjectProperty()
    _primary_color = ListProperty()
Beispiel #23
0
class LabelB(Label):  #class MyLabel(Label):
    bcolor = ListProperty([1, 1, 1, 1])
class ColorPicker(RelativeLayout):
    '''
    See module documentation.
    '''

    font_name = StringProperty('data/fonts/RobotoMono-Regular.ttf')
    '''Specifies the font used on the ColorPicker.

    :attr:`font_name` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'data/fonts/RobotoMono-Regular.ttf'.
    '''

    color = ListProperty((1, 1, 1, 1))
    '''The :attr:`color` holds the color currently selected in rgba format.

    :attr:`color` is a :class:`~kivy.properties.ListProperty` and defaults to
    (1, 1, 1, 1).
    '''

    hsv = ListProperty((1, 1, 1))
    '''The :attr:`hsv` holds the color currently selected in hsv format.

    :attr:`hsv` is a :class:`~kivy.properties.ListProperty` and defaults to
    (1, 1, 1).
    '''
    def _get_hex(self):
        return get_hex_from_color(self.color)

    def _set_hex(self, value):
        self.color = get_color_from_hex(value)[:4]

    hex_color = AliasProperty(_get_hex, _set_hex, bind=('color', ))
    '''The :attr:`hex_color` holds the currently selected color in hex.

    :attr:`hex_color` is an :class:`~kivy.properties.AliasProperty` and
    defaults to `#ffffffff`.
    '''

    wheel = ObjectProperty(None)
    '''The :attr:`wheel` holds the color wheel.

    :attr:`wheel` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.
    '''

    # now used only internally.
    foreground_color = ListProperty((1, 1, 1, 1))

    def on_color(self, instance, value):
        if not self._updating_clr:
            self._updating_clr = True
            self.hsv = rgb_to_hsv(*value[:3])
            self._updating_clr = False

    def on_hsv(self, instance, value):
        if not self._updating_clr:
            self._updating_clr = True
            self.color[:3] = hsv_to_rgb(*value)
            self._updating_clr = False

    def _trigger_update_clr(self, mode, clr_idx, text):
        self._upd_clr_list = mode, clr_idx, text
        Clock.unschedule(self._update_clr)
        Clock.schedule_once(self._update_clr)

    def _update_clr(self, dt):
        mode, clr_idx, text = self._upd_clr_list
        try:
            text = min(255, max(0, float(text)))
            if mode == 'rgb':
                self.color[clr_idx] = float(text) / 255.
            else:
                self.hsv[clr_idx] = float(text) / 255.
        except ValueError:
            Logger.warning('ColorPicker: invalid value : {}'.format(text))

    def _update_hex(self, dt):
        if len(self._upd_hex_list) != 9:
            return
        self.hex_color = self._upd_hex_list

    def _trigger_update_hex(self, text):
        self._upd_hex_list = text
        Clock.unschedule(self._update_hex)
        Clock.schedule_once(self._update_hex)

    def __init__(self, **kwargs):
        self._updating_clr = False
        super(ColorPicker, self).__init__(**kwargs)
class CircularTimePicker(BoxLayout, ThemableBehavior):
    """Widget that makes use of :class:`CircularHourPicker` and
    :class:`CircularMinutePicker` to create a user-friendly, animated
    time picker like the one seen on Android.

    See module documentation for more details.
    """

    primary_dark = ListProperty([1, 1, 1])

    hours = NumericProperty(0)
    """The hours, in military format (0-23).

    :attr:`hours` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0 (12am).
    """

    minutes = NumericProperty(0)
    """The minutes.

    :attr:`minutes` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    """

    time_list = ReferenceListProperty(hours, minutes)
    """Packs :attr:`hours` and :attr:`minutes` in a list for convenience.

    :attr:`time_list` is a :class:`~kivy.properties.ReferenceListProperty`.
    """

    # military = BooleanProperty(False)
    time_format = StringProperty(
        "[color={hours_color}][ref=hours]{hours}[/ref][/color][color={primary_dark}][ref=colon]:[/ref][/color]\
[color={minutes_color}][ref=minutes]{minutes:02d}[/ref][/color]")
    """String that will be formatted with the time and shown in the time label.
    Can be anything supported by :meth:`str.format`. Make sure you don't
    remove the refs. See the default for the arguments passed to format.
    :attr:`time_format` is a :class:`~kivy.properties.StringProperty` and
    defaults to "[color={hours_color}][ref=hours]{hours}[/ref][/color]:[color={minutes_color}][ref=minutes]\
        {minutes:02d}[/ref][/color]".
    """

    ampm_format = StringProperty(
        "[color={am_color}][ref=am]AM[/ref][/color]\n[color={pm_color}][ref=pm]PM[/ref][/color]"
    )
    """String that will be formatted and shown in the AM/PM label.
    Can be anything supported by :meth:`str.format`. Make sure you don't
    remove the refs. See the default for the arguments passed to format.

    :attr:`ampm_format` is a :class:`~kivy.properties.StringProperty` and
    defaults to "[color={am_color}][ref=am]AM[/ref][/color]\n[color={pm_color}][ref=pm]PM[/ref][/color]".
    """

    picker = OptionProperty("hours", options=("minutes", "hours"))
    """Currently shown time picker. Can be one of "minutes", "hours".

    :attr:`picker` is a :class:`~kivy.properties.OptionProperty` and
    defaults to "hours".
    """

    # selector_color = ListProperty([.337, .439, .490])
    selector_color = ListProperty([0, 0, 0])
    """Color of the number selector and of the highlighted text. RGB.

    :attr:`selector_color` is a :class:`~kivy.properties.ListProperty` and
    defaults to [.337, .439, .490] (material green).
    """

    color = ListProperty([1, 1, 1])
    """Color of the number labels and of the center dot. RGB.

    :attr:`color` is a :class:`~kivy.properties.ListProperty` and
    defaults to [1, 1, 1] (white).
    """

    selector_alpha = BoundedNumericProperty(.3, min=0, max=1)
    """Alpha value for the transparent parts of the selector.

    :attr:`selector_alpha` is a :class:`~kivy.properties.BoundedNumericProperty` and
    defaults to 0.3 (min=0, max=1).
    """

    _am = BooleanProperty(True)
    _h_picker = ObjectProperty(None)
    _m_picker = ObjectProperty(None)
    _bound = DictProperty({})

    def _get_time(self):
        try:
            return datetime.time(*self.time_list)
        except ValueError:
            self.time_list = [self.hours, 0]
            return datetime.time(*self.time_list)

    def set_time(self, dt):
        if dt.hour >= 12:
            dt.strftime("%I:%M")
            self._am = False
        self.time_list = [dt.hour, dt.minute]

    time = AliasProperty(_get_time, set_time, bind=("time_list", ))
    """Selected time as a datetime.time object.

    :attr:`time` is an :class:`~kivy.properties.AliasProperty`.
    """

    def _get_picker(self):
        if self.picker == "hours":
            return self._h_picker
        return self._m_picker

    _picker = AliasProperty(_get_picker, None)

    def _get_time_text(self):
        hc = rgb_to_hex(0, 0, 0) if self.picker == "hours" else rgb_to_hex(
            *self.primary_dark)
        mc = rgb_to_hex(0, 0, 0) if self.picker == "minutes" else rgb_to_hex(
            *self.primary_dark)
        h = self.hours == 0 and 12 or self.hours <= 12 and self.hours or self.hours - 12
        m = self.minutes
        primary_dark = rgb_to_hex(*self.primary_dark)
        return self.time_format.format(hours_color=hc,
                                       minutes_color=mc,
                                       hours=h,
                                       minutes=m,
                                       primary_dark=primary_dark)

    time_text = AliasProperty(_get_time_text,
                              None,
                              bind=("hours", "minutes", "time_format",
                                    "picker"))

    def _get_ampm_text(self, *args):
        amc = rgb_to_hex(0, 0, 0) if self._am else rgb_to_hex(
            *self.primary_dark)
        pmc = rgb_to_hex(0, 0, 0) if not self._am else rgb_to_hex(
            *self.primary_dark)
        return self.ampm_format.format(am_color=amc, pm_color=pmc)

    ampm_text = AliasProperty(_get_ampm_text,
                              None,
                              bind=("hours", "ampm_format", "_am"))

    def __init__(self, **kw):
        super(CircularTimePicker, self).__init__(**kw)
        self.selector_color = self.theme_cls.primary_color[0], self.theme_cls.primary_color[1], \
            self.theme_cls.primary_color[2]
        self.color = self.theme_cls.text_color
        self.primary_dark = self.theme_cls.primary_dark[0] / 2, self.theme_cls.primary_dark[1] / 2, \
            self.theme_cls.primary_dark[2] / 2
        self.on_ampm()
        if self.hours >= 12:
            self._am = False
        self.bind(time_list=self.on_time_list,
                  picker=self._switch_picker,
                  _am=self.on_ampm,
                  primary_dark=self._get_ampm_text)
        self._h_picker = CircularHourPicker()
        self.h_picker_touch = False
        self._m_picker = CircularMinutePicker()
        self.animating = False
        Clock.schedule_once(self.on_selected)
        Clock.schedule_once(self.on_time_list)
        Clock.schedule_once(self._init_later)
        Clock.schedule_once(lambda *a: self._switch_picker(noanim=True))

    def _init_later(self, *args):
        self.ids.timelabel.bind(on_ref_press=self.on_ref_press)
        self.ids.ampmlabel.bind(on_ref_press=self.on_ref_press)

    def on_ref_press(self, ign, ref):
        if not self.animating:
            if ref == "hours":
                self.picker = "hours"
            elif ref == "minutes":
                self.picker = "minutes"
        if ref == "am":
            self._am = True
        elif ref == "pm":
            self._am = False

    def on_selected(self, *a):
        if not self._picker:
            return
        if self.picker == "hours":
            hours = self._picker.selected if self._am else self._picker.selected + 12
            if hours == 24 and not self._am:
                hours = 12
            elif hours == 12 and self._am:
                hours = 0
            self.hours = hours
        elif self.picker == "minutes":
            self.minutes = self._picker.selected

    def on_time_list(self, *a):
        if not self._picker:
            return
        self._h_picker.selected = self.hours == 0 and 12 or self._am and self.hours or self.hours - 12
        self._m_picker.selected = self.minutes
        self.on_selected()

    def on_ampm(self, *a):
        if self._am:
            self.hours = self.hours if self.hours < 12 else self.hours - 12
        else:
            self.hours = self.hours if self.hours >= 12 else self.hours + 12

    def is_animating(self, *args):
        self.animating = True

    def is_not_animating(self, *args):
        self.animating = False

    def on_touch_down(self, touch):
        if not self._h_picker.collide_point(*touch.pos):
            self.h_picker_touch = False
        else:
            self.h_picker_touch = True
        super(CircularTimePicker, self).on_touch_down(touch)

    def on_touch_up(self, touch):
        try:
            if not self.h_picker_touch:
                return
            if not self.animating:
                if touch.grab_current is not self:
                    if self.picker == "hours":
                        self.picker = "minutes"
        except AttributeError:
            pass
        super(CircularTimePicker, self).on_touch_up(touch)

    def _switch_picker(self, *a, **kw):
        noanim = "noanim" in kw
        if noanim:
            noanim = kw["noanim"]

        try:
            container = self.ids.picker_container
        except (AttributeError, NameError):
            Clock.schedule_once(lambda *a: self._switch_picker(noanim=noanim))

        if self.picker == "hours":
            picker = self._h_picker
            prevpicker = self._m_picker
        elif self.picker == "minutes":
            picker = self._m_picker
            prevpicker = self._h_picker

        if len(self._bound) > 0:
            prevpicker.unbind(selected=self.on_selected)
            self.unbind(**self._bound)
        picker.bind(selected=self.on_selected)
        self._bound = {
            "selector_color": picker.setter("selector_color"),
            "color": picker.setter("color"),
            "selector_alpha": picker.setter("selector_alpha")
        }
        self.bind(**self._bound)

        if len(container._bound) > 0:
            container.unbind(**container._bound)
        container._bound = {
            "size": picker.setter("size"),
            "pos": picker.setter("pos")
        }
        container.bind(**container._bound)

        picker.pos = container.pos
        picker.size = container.size
        picker.selector_color = self.selector_color
        picker.color = self.color
        picker.selector_alpha = self.selector_alpha
        if noanim:
            if prevpicker in container.children:
                container.remove_widget(prevpicker)
            if picker.parent:
                picker.parent.remove_widget(picker)
            container.add_widget(picker)
        else:
            self.is_animating()
            if prevpicker in container.children:
                anim = Animation(scale=1.5, d=.5, t="in_back") & Animation(
                    opacity=0, d=.5, t="in_cubic")
                anim.start(prevpicker)
                Clock.schedule_once(
                    lambda *y: container.remove_widget(prevpicker), .5)  # .31)
            picker.scale = 1.5
            picker.opacity = 0
            if picker.parent:
                picker.parent.remove_widget(picker)
            container.add_widget(picker)
            anim = Animation(scale=1, d=.5, t="out_back") & Animation(
                opacity=1, d=.5, t="out_cubic")
            anim.bind(on_complete=self.is_not_animating)
            Clock.schedule_once(lambda *y: anim.start(picker), .3)
class ColorWheel(Widget):
    '''Chromatic wheel for the ColorPicker.

    .. versionchanged:: 1.7.1
        `font_size`, `font_name` and `foreground_color` have been removed. The
        sizing is now the same as others widget, based on 'sp'. Orientation is
        also automatically determined according to the width/height ratio.

    '''

    r = BoundedNumericProperty(0, min=0, max=1)
    '''The Red value of the color currently selected.

    :attr:`r` is a :class:`~kivy.properties.BoundedNumericProperty` and
    can be a value from 0 to 1. It defaults to 0.
    '''

    g = BoundedNumericProperty(0, min=0, max=1)
    '''The Green value of the color currently selected.

    :attr:`g` is a :class:`~kivy.properties.BoundedNumericProperty`
    and can be a value from 0 to 1.
    '''

    b = BoundedNumericProperty(0, min=0, max=1)
    '''The Blue value of the color currently selected.

    :attr:`b` is a :class:`~kivy.properties.BoundedNumericProperty` and
    can be a value from 0 to 1.
    '''

    a = BoundedNumericProperty(0, min=0, max=1)
    '''The Alpha value of the color currently selected.

    :attr:`a` is a :class:`~kivy.properties.BoundedNumericProperty` and
    can be a value from 0 to 1.
    '''

    color = ReferenceListProperty(r, g, b, a)
    '''The holds the color currently selected.

    :attr:`color` is a :class:`~kivy.properties.ReferenceListProperty` and
    contains a list of `r`, `g`, `b`, `a` values.
    '''

    _origin = ListProperty((100, 100))
    _radius = NumericProperty(100)

    _piece_divisions = NumericProperty(10)
    _pieces_of_pie = NumericProperty(16)

    _inertia_slowdown = 1.25
    _inertia_cutoff = .25

    _num_touches = 0
    _pinch_flag = False

    _hsv = ListProperty([1, 1, 1, 0])

    def __init__(self, **kwargs):
        super(ColorWheel, self).__init__(**kwargs)

        pdv = self._piece_divisions
        self.sv_s = [(float(x) / pdv, 1)
                     for x in range(pdv)] + [(1, float(y) / pdv)
                                             for y in reversed(range(pdv))]

    def on__origin(self, instance, value):
        self.init_wheel(None)

    def on__radius(self, instance, value):
        self.init_wheel(None)

    def init_wheel(self, dt):
        # initialize list to hold all meshes
        self.canvas.clear()
        self.arcs = []
        self.sv_idx = 0
        pdv = self._piece_divisions
        ppie = self._pieces_of_pie

        for r in range(pdv):
            for t in range(ppie):
                self.arcs.append(
                    _ColorArc(self._radius * (float(r) / float(pdv)),
                              self._radius * (float(r + 1) / float(pdv)),
                              2 * pi * (float(t) / float(ppie)),
                              2 * pi * (float(t + 1) / float(ppie)),
                              origin=self._origin,
                              color=(float(t) / ppie,
                                     self.sv_s[self.sv_idx + r][0],
                                     self.sv_s[self.sv_idx + r][1], 1)))

                self.canvas.add(self.arcs[-1])

    def recolor_wheel(self):
        ppie = self._pieces_of_pie
        for idx, segment in enumerate(self.arcs):
            segment.change_color(sv=self.sv_s[int(self.sv_idx + idx / ppie)])

    def change_alpha(self, val):
        for idx, segment in enumerate(self.arcs):
            segment.change_color(a=val)

    def inertial_incr_sv_idx(self, dt):
        # if its already zoomed all the way out, cancel the inertial zoom
        if self.sv_idx == len(self.sv_s) - self._piece_divisions:
            return False

        self.sv_idx += 1
        self.recolor_wheel()
        if dt * self._inertia_slowdown > self._inertia_cutoff:
            return False
        else:
            Clock.schedule_once(self.inertial_incr_sv_idx,
                                dt * self._inertia_slowdown)

    def inertial_decr_sv_idx(self, dt):
        # if its already zoomed all the way in, cancel the inertial zoom
        if self.sv_idx == 0:
            return False
        self.sv_idx -= 1
        self.recolor_wheel()
        if dt * self._inertia_slowdown > self._inertia_cutoff:
            return False
        else:
            Clock.schedule_once(self.inertial_decr_sv_idx,
                                dt * self._inertia_slowdown)

    def on_touch_down(self, touch):
        r = self._get_touch_r(touch.pos)
        if r > self._radius:
            return False

        # code is still set up to allow pinch to zoom, but this is
        # disabled for now since it was fiddly with small wheels.
        # Comment out these lines and  adjust on_touch_move to reenable
        # this.
        if self._num_touches != 0:
            return False

        touch.grab(self)
        self._num_touches += 1
        touch.ud['anchor_r'] = r
        touch.ud['orig_sv_idx'] = self.sv_idx
        touch.ud['orig_time'] = Clock.get_time()

    def on_touch_move(self, touch):
        if touch.grab_current is not self:
            return
        r = self._get_touch_r(touch.pos)
        goal_sv_idx = (touch.ud['orig_sv_idx'] - int(
            (r - touch.ud['anchor_r']) /
            (float(self._radius) / self._piece_divisions)))

        if (goal_sv_idx != self.sv_idx and goal_sv_idx >= 0
                and goal_sv_idx <= len(self.sv_s) - self._piece_divisions):
            # this is a pinch to zoom
            self._pinch_flag = True
            self.sv_idx = goal_sv_idx
            self.recolor_wheel()

    def on_touch_up(self, touch):
        if touch.grab_current is not self:
            return
        touch.ungrab(self)
        self._num_touches -= 1
        if self._pinch_flag:
            if self._num_touches == 0:
                # user was pinching, and now both fingers are up. Return
                # to normal
                if self.sv_idx > touch.ud['orig_sv_idx']:
                    Clock.schedule_once(
                        self.inertial_incr_sv_idx,
                        (Clock.get_time() - touch.ud['orig_time']) /
                        (self.sv_idx - touch.ud['orig_sv_idx']))

                if self.sv_idx < touch.ud['orig_sv_idx']:
                    Clock.schedule_once(
                        self.inertial_decr_sv_idx,
                        (Clock.get_time() - touch.ud['orig_time']) /
                        (self.sv_idx - touch.ud['orig_sv_idx']))

                self._pinch_flag = False
                return
            else:
                # user was pinching, and at least one finger remains. We
                # don't want to treat the remaining fingers as touches
                return
        else:
            r, theta = rect_to_polar(self._origin, *touch.pos)
            # if touch up is outside the wheel, ignore
            if r >= self._radius:
                return
            # compute which ColorArc is being touched (they aren't
            # widgets so we don't get collide_point) and set
            # _hsv based on the selected ColorArc
            piece = int((theta / (2 * pi)) * self._pieces_of_pie)
            division = int((r / self._radius) * self._piece_divisions)
            self._hsv = \
                self.arcs[self._pieces_of_pie * division + piece].color

    def on__hsv(self, instance, value):
        c_hsv = Color(*value, mode='hsv')
        self.r = c_hsv.r
        self.g = c_hsv.g
        self.b = c_hsv.b
        self.a = c_hsv.a
        self.rgba = (self.r, self.g, self.b, self.a)

    def _get_touch_r(self, pos):
        return distance(pos, self._origin)
Beispiel #27
0
class Triangle(Widget):
    """A triangle widget."""

    widget_type_name = 'Triangle'
    animation_properties = ('points', 'color', 'opacity', 'rotation', 'scale')

    def __init__(self,
                 mc: "MpfMc",
                 config: dict,
                 key: Optional[str] = None,
                 **kwargs) -> None:
        del kwargs
        super().__init__(mc=mc, config=config, key=key)

        # The points in this widget are always relative to the bottom left corner
        self.anchor_pos = ("left", "bottom")

        # Bind to all properties that when changed need to force
        # the widget to be redrawn
        self.bind(color=self._draw_widget,
                  points=self._draw_widget,
                  rotation=self._draw_widget,
                  scale=self._draw_widget)

        self._draw_widget()

    def _draw_widget(self, *args) -> None:
        """Establish the drawing instructions for the widget."""
        del args

        if self.canvas is None:
            return

        # TODO: allow user to set rotation/scale origin
        center = center_of_points_list(self.points)
        self.canvas.clear()

        with self.canvas:
            Color(*self.color)
            Scale(self.scale, origin=center)
            Rotate(angle=self.rotation, origin=center)
            KivyTriangle(points=self.points)

    #
    # Properties
    #

    points = ListProperty([0, 0, 50, 100, 100, 0])
    '''The list of points to use to draw the widget in (x1, y1, x2, y2,
    x3, y3) format.

    :attr:`points` is a :class:`~kivy.properties.ListProperty`.
    '''

    rotation = NumericProperty(0)
    '''Rotation angle value of the widget.

    :attr:`rotation` is an :class:`~kivy.properties.NumericProperty` and defaults to
    0.
    '''

    scale = NumericProperty(1.0)
    '''Scale value of the widget.
Beispiel #28
0
class DiscordToolbar(CustomToolbar, DiscordTheme):
    text_color = ListProperty()
    title = StringProperty()
    font_size = NumericProperty("20dp")
Beispiel #29
0
class TreeViewNode(object):
    '''TreeViewNode class, used to build a node class for a TreeView object.
    '''
    def __init__(self, **kwargs):
        if self.__class__ is TreeViewNode:
            raise TreeViewException('You cannot use directly TreeViewNode.')
        super(TreeViewNode, self).__init__(**kwargs)

    is_leaf = BooleanProperty(True)
    '''Boolean to indicate whether this node is a leaf or not. Used to adjust
    the graphical representation.

    :attr:`is_leaf` is a :class:`~kivy.properties.BooleanProperty` and defaults
    to True. It is automatically set to False when child is added.
    '''

    is_open = BooleanProperty(False)
    '''Boolean to indicate whether this node is opened or not, in case there
    are child nodes. This is used to adjust the graphical representation.

    .. warning::

        This property is automatically set by the :class:`TreeView`. You can
        read but not write it.

    :attr:`is_open` is a :class:`~kivy.properties.BooleanProperty` and defaults
    to False.
    '''

    is_loaded = BooleanProperty(False)
    '''Boolean to indicate whether this node is already loaded or not. This
    property is used only if the :class:`TreeView` uses asynchronous loading.

    :attr:`is_loaded` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    is_selected = BooleanProperty(False)
    '''Boolean to indicate whether this node is selected or not. This is used
    adjust the graphical representation.

    .. warning::

        This property is automatically set by the :class:`TreeView`. You can
        read but not write it.

    :attr:`is_selected` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    no_selection = BooleanProperty(False)
    '''Boolean used to indicate whether selection of the node is allowed or
     not.

    :attr:`no_selection` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    nodes = ListProperty([])
    '''List of nodes. The nodes list is different than the children list. A
    node in the nodes list represents a node on the tree. An item in the
    children list represents the widget associated with the node.

    .. warning::

        This property is automatically set by the :class:`TreeView`. You can
        read but not write it.

    :attr:`nodes` is a :class:`~kivy.properties.ListProperty` and defaults to
    [].
    '''

    parent_node = ObjectProperty(None, allownone=True)
    '''Parent node. This attribute is needed because the :attr:`parent` can be
    None when the node is not displayed.

    .. versionadded:: 1.0.7

    :attr:`parent_node` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.
    '''

    level = NumericProperty(-1)
    '''Level of the node.

    :attr:`level` is a :class:`~kivy.properties.NumericProperty` and defaults
    to -1.
    '''

    color_selected = ListProperty([.3, .3, .3, 1.])
    '''Background color of the node when the node is selected.

    :attr:`color_selected` is a :class:`~kivy.properties.ListProperty` and
    defaults to [.1, .1, .1, 1].
    '''

    odd = BooleanProperty(False)
    '''
    This property is set by the TreeView widget automatically and is read-only.

    :attr:`odd` is a :class:`~kivy.properties.BooleanProperty` and defaults to
    False.
    '''

    odd_color = ListProperty([1., 1., 1., .0])
    '''Background color of odd nodes when the node is not selected.

    :attr:`odd_color` is a :class:`~kivy.properties.ListProperty` and defaults
    to [1., 1., 1., 0.].
    '''

    even_color = ListProperty([0.5, 0.5, 0.5, 0.1])
    '''Background color of even nodes when the node is not selected.
Beispiel #30
0
class AKFloatingRoundedAppbarButtonItem(AKFloatingRoundedAppbarItemBase):
    text_color = ListProperty()
    icon_color = ListProperty()
    icon = StringProperty()
    text = StringProperty()