class AKFloatingRoundedAppbarAvatarItem(AKFloatingRoundedAppbarItemBase): source = StringProperty() text = StringProperty() text_color = ListProperty()
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)
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)
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
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)
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")
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)
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
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)
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()
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
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
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
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()
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)
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
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)
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()
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))
class UserAnimationCard(ThemableBehavior, FloatLayout): user_name = StringProperty() path_to_avatar = StringProperty() _callback_back = ObjectProperty() _primary_color = ListProperty()
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)
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.
class DiscordToolbar(CustomToolbar, DiscordTheme): text_color = ListProperty() title = StringProperty() font_size = NumericProperty("20dp")
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.
class AKFloatingRoundedAppbarButtonItem(AKFloatingRoundedAppbarItemBase): text_color = ListProperty() icon_color = ListProperty() icon = StringProperty() text = StringProperty()