def test_numericcheck(self): from kivy.properties import NumericProperty a = NumericProperty() a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) a.set(wid, 99) self.assertEqual(a.get(wid), 99)
def test_numeric_string_without_units(self): from kivy.properties import NumericProperty a = NumericProperty() a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) a.set(wid, '2') self.assertEqual(a.get(wid), 2)
def test_propertynone(self): from kivy.properties import NumericProperty a = NumericProperty(0, allownone=True) a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) try: a.set(wid, None) self.assertEqual(a.get(wid), None) except ValueError, e: pass
def test_reference_child_update(self): from kivy.properties import NumericProperty, ReferenceListProperty x = NumericProperty(0) x.link(wid, 'x') x.link_deps(wid, 'x') y = NumericProperty(0) y.link(wid, 'y') y.link_deps(wid, 'y') pos = ReferenceListProperty(x, y) pos.link(wid, 'pos') pos.link_deps(wid, 'pos') pos.get(wid)[0] = 10 self.assertEqual(pos.get(wid), [10, 0]) pos.get(wid)[:] = (20, 30) self.assertEqual(pos.get(wid), [20, 30])
class Knob(Widget): """Class for creating a Knob widget.""" min = NumericProperty(0) '''Minimum value for value :attr:`value`. :attr:`min` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' max = NumericProperty(100) '''Maximum value for value :attr:`value`. :attr:`max` is a :class:`~kivy.properties.NumericProperty` and defaults to 100. ''' range = ReferenceListProperty(min, max) ''' Range of the values for Knob. :attr:`range` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`min`, :attr:`max`). ''' value = NumericProperty(0) '''Current value of the knob. Set :attr:`value` when creating a knob to set its initial position. An internal :attr:`_angle` is calculated to set the position of the knob. :attr:`value` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' step = BoundedNumericProperty(1, min=0) '''Step interval of knob to go from min to max. An internal :attr:`_angle_step` is calculated to set knob step in degrees. :attr:`step` is a :class:`~kivy.properties.BoundedNumericProperty` and defaults to 1 (min=0). ''' curve = BoundedNumericProperty(1, min=1) '''This parameter determines the shape of the map function. It represent the reciprocal of a power function's exponent used to map the input value. So, for higher values of curve the contol is more reactive, and conversely. ''' knobimg_source = StringProperty("") '''Path of texture image that visually represents the knob. Use PNG for transparency support. The texture is rendered on a centered rectangle of size = :attr:`size` * :attr:`knobimg_size`. :attr:`knobimg_source` is a :class:`~kivy.properties.StringProperty` and defaults to empty string. ''' knobimg_color = ListProperty([1, 1, 1, 1]) '''Color to apply to :attr:`knobimg_source` texture when loaded. :attr:`knobimg_color` is a :class:`~kivy.properties.ListProperty` and defaults to [1,1,1,1]. ''' knobimg_size = BoundedNumericProperty(1.0, max=1.0, min=0.1) ''' Internal proportional size of rectangle that holds :attr:`knobimg_source` texture. :attr:`knobimg_size` is a :class:`~kivy.properties.BoundedNumericProperty` and defaults to 0.9. ''' show_marker = BooleanProperty(True) ''' Shows/hides marker surrounding knob. use :attr:`knob_size` < 1.0 to leave space to marker. :attr:`show_marker` is a :class:`~kivy.properties.BooleanProperty` and defaults to True. ''' marker_img = StringProperty("") '''Path of texture image that visually represents the knob marker. The marker is rendered in a centered Ellipse (Circle) with the same size of the widget and goes from angle_start=:attr:`marker_startangle` to angle_end=:attr:`_angle`. :attr:`marker_img` is a :class:`~kivy.properties.StringProperty` and defaults to "". ''' marker_color = ListProperty([1, 1, 1, 1]) '''Color to apply to :attr:`marker_img` texture when loaded. :attr:`marker_color` is a :class:`~kivy.properties.ListProperty` and defaults to [1,1,1,1]. ''' knobimg_bgcolor = ListProperty([0, 0, 0, 1]) ''' Background color behind :attr:`knobimg_source` texture. :attr:`value` is a :class:`~kivy.properties.ListProperty` and defaults to [0,0,0,1]. ''' markeroff_img = StringProperty("") '''Path of texture image that visually represents the knob marker where it's off, that is, parts of the marker that haven't been reached yet by the knob (:attr:`value`). :attr:`markeroff_img` is a :class:`~kivy.properties.StringProperty` and defaults to "". ''' markeroff_color = ListProperty([0, 0, 0, 0]) '''Color applied to :attr:`markeroff_img` int the Canvas. :attr:`markeroff_color` is a :class:`~kivy.properties.ListProperty` and defaults to [0,0,0,0]. ''' marker_startangle = NumericProperty(0) '''Starting angle of Ellipse where :attr:`marker_img` is rendered. :attr:`value` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' marker_ahead = NumericProperty(0) ''' Adds degrees to angle_end of marker (except when :attr:`value` == 0). :attr:`marker_ahead` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' _angle = NumericProperty(0) # Internal angle calculated from value. _angle_step = NumericProperty( 0) # Internal angle_step calculated from step. def __init__(self, *args, **kwargs): super(Knob, self).__init__(*args, **kwargs) self.bind(show_marker=self._show_marker) self.bind(value=self._value) def _value(self, instance, value): self._angle = pow( (value - self.min) / (self.max - self.min), 1. / self.curve) * 360. self.on_knob(value) def _show_marker(self, instance, flag): # "show/hide" marker. if flag: self.knobimg_bgcolor[3] = 1 self.marker_color[3] = 1 self.markeroff_color[3] = 1 else: self.knobimg_bgcolor[3] = 0 self.marker_color[3] = 0 self.markeroff_color[3] = 0 def on_touch_down(self, touch): if self.collide_point(*touch.pos): self.update_angle(touch) def on_touch_move(self, touch): if self.collide_point(*touch.pos): self.update_angle(touch) def update_angle(self, touch): posx, posy = touch.pos cx, cy = self.center rx, ry = posx - cx, posy - cy if ry >= 0: # Quadrants are clockwise. quadrant = 1 if rx >= 0 else 4 else: quadrant = 3 if rx <= 0 else 2 try: angle = math.atan(rx / ry) * (180. / math.pi) if quadrant == 2 or quadrant == 3: angle = 180 + angle elif quadrant == 4: angle = 360 + angle except: # atan not def for angle 90 and 270 angle = 90 if quadrant <= 2 else 270 self._angle_step = (self.step * 360) / (self.max - self.min) self._angle = self._angle_step while self._angle < angle: self._angle = self._angle + self._angle_step relativeValue = pow((angle / 360.), 1. / self.curve) self.value = (relativeValue * (self.max - self.min)) + self.min #TO OVERRIDE def on_knob(self, value): pass
def test_numeric_string_with_units_check(self): from kivy.properties import NumericProperty a = NumericProperty() a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) a.set(wid, '55dp') self.assertEqual(a.get(wid), 55) self.assertEqual(a.get_format(wid), 'dp') a.set(wid, u'55dp') self.assertEqual(a.get(wid), 55) self.assertEqual(a.get_format(wid), 'dp') a.set(wid, '99in') self.assertEqual(a.get(wid), 9504.0) self.assertEqual(a.get_format(wid), 'in') a.set(wid, u'99in') self.assertEqual(a.get(wid), 9504.0) self.assertEqual(a.get_format(wid), 'in')
class AccordionItem(FloatLayout): '''AccordionItem class that must be used in conjunction with the :class:`Accordion` class. See the module documentation for more information. ''' title = StringProperty('') '''Title string of the item. The title might be used in conjunction with the `AccordionItemTitle` template. If you are using a custom template, you can use that property as a text entry, or not. By default, it's used for the title text. See title_template and the example below. :attr:`title` is a :class:`~kivy.properties.StringProperty` and defaults to ''. ''' title_template = StringProperty('AccordionItemTitle') '''Template to use for creating the title part of the accordion item. The default template is a simple Label, not customizable (except the text) that supports vertical and horizontal orientation and different backgrounds for collapse and selected mode. It's better to create and use your own template if the default template does not suffice. :attr:`title` is a :class:`~kivy.properties.StringProperty` and defaults to 'AccordionItemTitle'. The current default template lives in the `kivy/data/style.kv` file. Here is the code if you want to build your own template:: [AccordionItemTitle@Label]: text: ctx.title canvas.before: Color: rgb: 1, 1, 1 BorderImage: source: ctx.item.background_normal \ if ctx.item.collapse \ else ctx.item.background_selected pos: self.pos size: self.size PushMatrix Translate: xy: self.center_x, self.center_y Rotate: angle: 90 if ctx.item.orientation == 'horizontal' else 0 axis: 0, 0, 1 Translate: xy: -self.center_x, -self.center_y canvas.after: PopMatrix ''' title_args = DictProperty({}) '''Default arguments that will be passed to the :meth:`kivy.lang.Builder.template` method. :attr:`title_args` is a :class:`~kivy.properties.DictProperty` and defaults to {}. ''' collapse = BooleanProperty(True) '''Boolean to indicate if the current item is collapsed or not. :attr:`collapse` is a :class:`~kivy.properties.BooleanProperty` and defaults to True. ''' collapse_alpha = NumericProperty(1.) '''Value between 0 and 1 to indicate how much the item is collapsed (1) or whether it is selected (0). It's mostly used for animation. :attr:`collapse_alpha` is a :class:`~kivy.properties.NumericProperty` and defaults to 1. ''' accordion = ObjectProperty(None) '''Instance of the :class:`Accordion` that the item belongs to. :attr:`accordion` is an :class:`~kivy.properties.ObjectProperty` and defaults to None. ''' background_normal = StringProperty( 'atlas://data/images/defaulttheme/button') '''Background image of the accordion item used for the default graphical representation when the item is collapsed. :attr:`background_normal` is a :class:`~kivy.properties.StringProperty` and defaults to 'atlas://data/images/defaulttheme/button'. ''' background_disabled_normal = StringProperty( 'atlas://data/images/defaulttheme/button_disabled') '''Background image of the accordion item used for the default graphical representation when the item is collapsed and disabled. .. versionadded:: 1.8.0 :attr:`background__disabled_normal` is a :class:`~kivy.properties.StringProperty` and defaults to 'atlas://data/images/defaulttheme/button_disabled'. ''' background_selected = StringProperty( 'atlas://data/images/defaulttheme/button_pressed') '''Background image of the accordion item used for the default graphical representation when the item is selected (not collapsed). :attr:`background_normal` is a :class:`~kivy.properties.StringProperty` and defaults to 'atlas://data/images/defaulttheme/button_pressed'. ''' background_disabled_selected = StringProperty( 'atlas://data/images/defaulttheme/button_disabled_pressed') '''Background image of the accordion item used for the default graphical representation when the item is selected (not collapsed) and disabled. .. versionadded:: 1.8.0 :attr:`background_disabled_selected` is a :class:`~kivy.properties.StringProperty` and defaults to 'atlas://data/images/defaulttheme/button_disabled_pressed'. ''' orientation = OptionProperty('vertical', options=( 'horizontal', 'vertical')) '''Link to the :attr:`Accordion.orientation` property. ''' min_space = NumericProperty('44dp') '''Link to the :attr:`Accordion.min_space` property. ''' content_size = ListProperty([100, 100]) '''(internal) Set by the :class:`Accordion` to the size allocated for the content. ''' container = ObjectProperty(None) '''(internal) Property that will be set to the container of children inside the AccordionItem representation. ''' container_title = ObjectProperty(None) '''(internal) Property that will be set to the container of title inside the AccordionItem representation. ''' def __init__(self, **kwargs): self._trigger_title = Clock.create_trigger(self._update_title, -1) self._anim_collapse = None super(AccordionItem, self).__init__(**kwargs) trigger_title = self._trigger_title fbind = self.fbind fbind('title', trigger_title) fbind('title_template', trigger_title) fbind('title_args', trigger_title) trigger_title() def add_widget(self, widget): if self.container is None: return super(AccordionItem, self).add_widget(widget) return self.container.add_widget(widget) def remove_widget(self, widget): if self.container: self.container.remove_widget(widget) super(AccordionItem, self).remove_widget(widget) def on_collapse(self, instance, value): accordion = self.accordion if accordion is None: return if not value: self.accordion.select(self) collapse_alpha = float(value) if self._anim_collapse: self._anim_collapse.stop(self) self._anim_collapse = None if self.collapse_alpha != collapse_alpha: self._anim_collapse = Animation( collapse_alpha=collapse_alpha, t=accordion.anim_func, d=accordion.anim_duration) self._anim_collapse.start(self) def on_collapse_alpha(self, instance, value): self.accordion._trigger_layout() def on_touch_down(self, touch): if not self.collide_point(*touch.pos): return if self.disabled: return True if self.collapse: self.collapse = False return True else: return super(AccordionItem, self).on_touch_down(touch) def _update_title(self, dt): if not self.container_title: self._trigger_title() return c = self.container_title c.clear_widgets() instance = Builder.template(self.title_template, title=self.title, item=self, **self.title_args) c.add_widget(instance)
class GraphRotatedLabel(Label): angle = NumericProperty(0)
class Plot(EventDispatcher): '''Plot class, see module documentation for more information. :Events: `on_clear_plot` Fired before a plot updates the display and lets the fbo know that it should clear the old drawings. ..versionadded:: 0.4 ''' __events__ = ('on_clear_plot', ) # most recent values of the params used to draw the plot params = DictProperty({ 'xlog': False, 'xmin': 0, 'xmax': 100, 'ylog': False, 'ymin': 0, 'ymax': 100, 'size': (0, 0, 0, 0) }) color = ListProperty([1, 1, 1, 1]) '''Color of the plot. ''' points = ListProperty([]) '''List of (x, y) points to be displayed in the plot. The elements of points are 2-tuples, (x, y). The points are displayed based on the mode setting. :data:`points` is a :class:`~kivy.properties.ListProperty`, defaults to []. ''' x_axis = NumericProperty(0) '''Index of the X axis to use, defaults to 0 ''' y_axis = NumericProperty(0) '''Index of the Y axis to use, defaults to 0 ''' def __init__(self, **kwargs): super(Plot, self).__init__(**kwargs) self.ask_draw = Clock.create_trigger(self.draw) self.bind(params=self.ask_draw, points=self.ask_draw) self._drawings = self.create_drawings() def funcx(self): """Return a function that convert or not the X value according to plot prameters""" return log10 if self.params["xlog"] else lambda x: x def funcy(self): """Return a function that convert or not the Y value according to plot prameters""" return log10 if self.params["ylog"] else lambda y: y def x_px(self): """Return a function that convert the X value of the graph to the pixel coordinate on the plot, according to the plot settings and axis settings """ funcx = self.funcx() params = self.params size = params["size"] xmin = funcx(params["xmin"]) xmax = funcx(params["xmax"]) ratiox = (size[2] - size[0]) / float(xmax - xmin) return lambda x: (funcx(x) - xmin) * ratiox + size[0] def y_px(self): """Return a function that convert the Y value of the graph to the pixel coordinate on the plot, according to the plot settings and axis settings """ funcy = self.funcy() params = self.params size = params["size"] ymin = funcy(params["ymin"]) ymax = funcy(params["ymax"]) ratioy = (size[3] - size[1]) / float(ymax - ymin) return lambda y: (funcy(y) - ymin) * ratioy + size[1] def unproject(self, x, y): """Return a function that unproject a pixel to a X/Y value on the plot (works only for linear, not log yet) """ params = self.params size = params["size"] xmin = params["xmin"] xmax = params["xmax"] ymin = params["ymin"] ymax = params["ymax"] ratiox = (size[2] - size[0]) / float(xmax - xmin) ratioy = (size[3] - size[1]) / float(ymax - ymin) x0 = (x - size[0]) / ratiox + xmin y0 = (y - size[1]) / ratioy + ymin return x0, y0 def get_px_bounds(self): """Returns a dict containing the pixels bounds from the plot parameters """ params = self.params x_px = self.x_px() y_px = self.y_px() return { "xmin": x_px(params["xmin"]), "xmax": x_px(params["xmax"]), "ymin": y_px(params["ymin"]), "ymax": y_px(params["ymax"]), } def update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): '''Called by graph whenever any of the parameters change. The plot should be recalculated then. log, min, max indicate the axis settings. size a 4-tuple describing the bounding box in which we can draw graphs, it's (x0, y0, x1, y1), which correspond with the bottom left and top right corner locations, respectively. ''' self.params.update({ 'xlog': xlog, 'xmin': xmin, 'xmax': xmax, 'ylog': ylog, 'ymin': ymin, 'ymax': ymax, 'size': size }) def get_group(self): '''returns a string which is unique and is the group name given to all the instructions returned by _get_drawings. Graph uses this to remove these instructions when needed. ''' return '' def get_drawings(self): '''returns a list of canvas instructions that will be added to the graph's canvas. ''' if isinstance(self._drawings, (tuple, list)): return self._drawings return [] def create_drawings(self): '''called once to create all the canvas instructions needed for the plot ''' pass def draw(self, *largs): '''draw the plot according to the params. It dispatches on_clear_plot so derived classes should call super before updating. ''' self.dispatch('on_clear_plot') def iterate_points(self): '''Iterate on all the points adjusted to the graph settings ''' x_px = self.x_px() y_px = self.y_px() for x, y in self.points: yield x_px(x), y_px(y) def on_clear_plot(self, *largs): pass # compatibility layer _update = update _get_drawings = get_drawings _params = params
class SlugStats(BoxLayout): name = StringProperty('') wins = NumericProperty(0) win_percent = NumericProperty(0)
class SlugInfo(BoxLayout): y_position = NumericProperty(0) name = StringProperty('') wins = NumericProperty(0)
class MDSwiper(ScrollView, EventDispatcher): items_spacing = NumericProperty("20dp") """ The space between each :class:`MDSwiperItem`. :attr:`items_spacing` is an :class:`~kivy.properties.NumericProperty` and defaults to `20dp`. """ transition_duration = NumericProperty(0.2) """ Duration of switching between :class:`MDSwiperItem`. :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` and defaults to `0.2`. """ size_duration = NumericProperty(0.2) """ Duration of changing the size of :class:`MDSwiperItem`. :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` and defaults to `0.2`. """ size_transition = StringProperty("out_quad") """ The type of animation used for changing the size of :class:`MDSwiperItem`. :attr:`size_transition` is an :class:`~kivy.properties.StringProperty` and defaults to `out_quad`. """ swipe_transition = StringProperty("out_quad") """ The type of animation used for swiping. :attr:`swipe_transition` is an :class:`~kivy.properties.StringProperty` and defaults to `out_quad`. """ swipe_distance = NumericProperty("70dp") """ Distance to move before swiping the :class:`MDSwiperItem`. :attr:`swipe_distance` is an :class:`~kivy.properties.NumericProperty` and defaults to `70dp`. """ width_mult = NumericProperty(3) """ This number is multiplied by :attr:`items_spacing` x2 and then subtracted from the width of window to specify the width of :class:`MDSwiperItem`. So by decreasing the :attr:`width_mult` the width of :class:`MDSwiperItem` increases and vice versa. :attr:`width_mult` is an :class:`~kivy.properties.NumericProperty` and defaults to `3`. """ swipe_on_scroll = BooleanProperty(True) """ Wheter to swipe on mouse wheel scrolling or not. :attr:`swipe_on_scroll` is an :class:`~kivy.properties.BooleanProperty` and defaults to `True`. """ _selected = 0 _start_touch_x = None __events__ = ( "on_swipe", "on_pre_swipe", "on_overswipe_right", "on_overswipe_left", "on_swipe_left", "on_swipe_right", ) def __init__(self, **kwargs): super().__init__(**kwargs) self.register_event_type("on_swipe") self.register_event_type("on_pre_swipe") self.register_event_type("on_overswipe_right") self.register_event_type("on_overswipe_left") self.register_event_type("on_swipe_left") self.register_event_type("on_swipe_right") self.effect_cls = _ScrollViewHardStop def add_widget(self, widget, index=0): if issubclass(widget.__class__, MDSwiperItem): widget._root = self items_box = _ItemsBox(_root=self) items_box.add_widget(widget) self.ids.anchor_scroll.add_widget(items_box) return else: return super().add_widget(widget, index=index) def remove_widget(self, widget): if not issubclass(widget.__class__, MDSwiperItem): return for item_box in self.ids.anchor_scroll.children: if widget in item_box.children: return self.ids.anchor_scroll.remove_widget(item_box) def set_current(self, index): """Switch to given :class:`MDSwiperItem` index.""" self._selected = index self.dispatch("on_pre_swipe") self._reset_size() self.dispatch("on_swipe") def get_current_index(self): """Returns the current :class:`MDSwiperItem` index.""" return self._selected def get_current_item(self): """Returns the current :class:`MDSwiperItem` instance.""" return list(reversed( self.ids.anchor_scroll.children))[self._selected].children[0] def get_items(self): """Returns the list of :class:`MDSwiperItem` children. .. note:: Use `get_items()` to get the list of children instead of `MDSwiper.children`. """ children = list(reversed(self.ids.anchor_scroll.children)) items = [item.children[0] for item in children] return items def _reset_size(self, *args): children = list(reversed(self.ids.anchor_scroll.children)) if not children: return child = children[self._selected] total_width = self.ids.anchor_scroll.width - Window.width view_x = child.x - self.items_spacing * self.width_mult anim = Animation( scroll_x=view_x / total_width, d=self.transition_duration, t=self.swipe_transition, ) anim.start(self) for widget in children: widget.children[0]._dismiss_size() widget.children[0]._selected = False child.children[0]._selected_size() child.children[0]._selected = True def on_swipe(self): pass def on_pre_swipe(self): pass def on_overswipe_right(self): pass def on_overswipe_left(self): pass def on_swipe_left(self): pass def on_swipe_right(self): pass def swipe_left(self): previous_index = self._selected - 1 if previous_index == -1: self.set_current(0) self.dispatch("on_overswipe_left") else: self.set_current(previous_index) self.dispatch("on_swipe_left") def swipe_right(self): next_index = self._selected + 1 last_index = len(self.ids.anchor_scroll.children) - 1 if next_index == last_index + 1: self.set_current(last_index) self.dispatch("on_overswipe_right") else: self.set_current(next_index) self.dispatch("on_swipe_right") def on_scroll_start(self, touch, check_children=True): if platform in ["ios", "android"]: return super().on_scroll_start(touch) # on touch pad events if touch.button == "scrollright": self.swipe_left() elif touch.button == "scrollleft": self.swipe_right() return super().on_scroll_start(touch) def on_touch_down(self, touch): super().on_touch_down(touch) if not self.collide_point(touch.pos[0], touch.pos[1]): return if platform not in ["ios", "android"] and self.swipe_on_scroll: if touch.button == "scrolldown": self.swipe_right() elif touch.button == "scrollup": self.swipe_left() else: self._start_touch_x = touch.pos[0] else: self._start_touch_x = touch.pos[0] def on_touch_up(self, touch): super().on_touch_up(touch) if not self._start_touch_x: return if self._start_touch_x: touch_x_diff = abs(self._start_touch_x - touch.pos[0]) else: return if touch_x_diff <= self.swipe_distance: if touch_x_diff == 0: return self._reset_size() return # swipe to left if self._start_touch_x < touch.pos[0]: self.swipe_left() # swipe to right else: self.swipe_right() self._start_touch_x = None return
class ImageToModelScaler(Widget): """Use this class when you have an image inside a ScatterLayout and you need to convert coordinates from the Image widget to coordinates in the underlying image. If your widget coords are represented as (X, Y, height, width), use the cropobject_widget2model() method. If your widget coords are (top, left, bottom, right), use bbox_widget2model(). Note that in the second case, we expect top to be the widget-world top, so it will have a *larger* value than bottom. This second method can be used directly with the ('top', 'left', 'bottom', 'right') selection dictionary recorded by a BoundingBoxTracer. Note: when we say 'top', 'bottom', 'left' or 'right' in this context, we mean it in a WYSIWYG manner: the coordinate corresponding to the top boundary of the object you are seeing on the screen. If you need to map individual (X, Y) points: use point_widget2model and point_model2widget. """ widget_height = NumericProperty(1.0) widget_width = NumericProperty(1.0) model_height = NumericProperty(1.0) model_width = NumericProperty(1.0) def __init__(self, image_widget, image_model, **kwargs): """Initialize the widget. :param image_widget: The Image widget. Expects coordinates to be bottom to top, left to right (counted from the bottom left corner). :param image_model: A numpy array of the actual image. Assumes shape[0] is height, shape[1] is width. """ super(ImageToModelScaler, self).__init__(**kwargs) # logging.info('Scaler: image widget: {0}'.format(image_widget)) # logging.info('Scaler: image widget parent: {0}'.format(image_widget.parent)) # logging.info('Scaler: image widget pparent: {0}'.format(image_widget.parent.parent)) self.reset(image_widget, image_model) def reset(self, image_widget, image_model): logging.info('Scaler: RESET') # Bind widget shape changes to our properties. image_widget.bind(height=self.set_widget_height) image_widget.bind(width=self.set_widget_width) self.widget_height = image_widget.height self.widget_width = image_widget.width logging.info('Scaler: Widget image shape: {0}'.format((self.widget_height, self.widget_width))) model_shape = image_model.shape self.model_height = model_shape[0] self.model_width = model_shape[1] logging.info('Scaler: Model image shape: {0}'.format(model_shape)) logging.info('Scaler: m2w ratios: {0}'.format((self.m2w_ratio_height, self.m2w_ratio_width))) logging.info('Scaler: w2m ratios: {0}'.format((self.w2m_ratio_height, self.w2m_ratio_width))) def cropobject_widget2model(self, wX, wY, wHeight, wWidth): raise NotImplementedError() def cropobject_model2widget(self, mX, wY, mHeight, mWidth): raise NotImplementedError() def bbox_widget2model(self, wTop, wLeft, wBottom, wRight): mTop = (self.widget_height - wTop) * self.w2m_ratio_height mBottom = (self.widget_height - wBottom) * self.w2m_ratio_height mLeft = wLeft * self.w2m_ratio_width mRight = wRight * self.w2m_ratio_width logging.info('Scaler: From widget: {0} to model: {1}. w2m ratios: {2}' ''.format((wTop, wLeft, wBottom, wRight), (mTop, mLeft, mBottom, mRight), (self.w2m_ratio_height, self.w2m_ratio_width))) return mTop, mLeft, mBottom, mRight def bbox_model2widget(self, mTop, mLeft, mBottom, mRight): wTop = self.widget_height - (mTop * self.m2w_ratio_height) wBottom = self.widget_height - (mBottom * self.m2w_ratio_height) wLeft = mLeft * self.m2w_ratio_width wRight = mRight * self.m2w_ratio_width return wTop, wLeft, wBottom, wRight def point_widget2model(self, wX, wY): """Maps a point from the widget (kivy) space to the model (numpy) space. :param wX: horizontal coordinate :param wY: vertical coordinate :returns: A tuple (mX, mY), where mX is the model *vertical* coordinate (row) and mY is the model *horizontal* coordinate (column). """ mX = (self.widget_height - wY) * self.w2m_ratio_height mY = wX * self.w2m_ratio_width return mX, mY def point_model2widget(self, mX, mY): """Maps a point from the widget (kivy) space to the model (numpy) space. :param mX: horizontal coordinate :param mY: vertical coordinate :returns: A tuple (wX, wY), where wX is the widget *horizontal* coordinate (column) and wY is the model *vertical* coordinate (row), measured from the *bottom*. """ raise NotImplementedError() ################################### # Listening to widget size changes. def set_widget_height(self, instance, pos): logging.info('Scaler: widget height change triggered, pos={0}'.format(pos)) self.widget_height = pos def set_widget_width(self, instance, pos): logging.info('Scaler: widget width change triggered, pos={0}'.format(pos)) self.widget_width = pos #################################### # Scaling ratios. # "m2w" is "multiply by this ratio when converting from Model to Widget". # "w2m" is "multiply by this ratio when converting from Widget to Model". @property def m2w_ratio_height(self): return self.widget_height * 1.0 / self.model_height @property def m2w_ratio_width(self): return self.widget_width * 1.0 / self.model_width @property def w2m_ratio_height(self): return self.model_height * 1.0 / self.widget_height @property def w2m_ratio_width(self): return self.model_width * 1.0 / self.widget_width
class Canvas3D(FloatLayout): adding_queue = [] '''adding_queue_doc ''' translate = ListProperty([0.0, 0.0, 0.0]) ''' Translate all children widgets around the value ''' rotate = ListProperty([0.0, 0.0, 0.0, 0.0]) ''' Rotate all children widgets around the value ''' _translate = None ''' Shadow FBO translate value ''' _translate_fbo = None ''' Shadow FBO translate value ''' _translate_picking = None ''' Picking FBO translate value ''' _translate_motion = None ''' Motion FBO translate value ''' nodes = [] ''' Nodes list ''' shadow = True ''' Shadow state, at now always the shadows is enable ''' picking = True ''' Allow picking. At now always the picking is enable ''' fbo = None ''' Shadow FBO ''' fbo_list = {} ''' List of elements attached to a Mesh ''' shadow_threshold = 1.0 ''' Shadow Distance ''' _update_fbo = 0 current_id = 0.01 '''Mesh ID counter ''' last_touch_pos = [-1, -1, -1, -1] '''last_touch_pos counter ''' perspective_value = NumericProperty(35.) '''Perspective value ''' def __init__(self, **kwargs): self.shadow = kwargs.get("shadow", False) global PICKING_BUFFER_SIZE PICKING_BUFFER_SIZE = kwargs.get("canvas_size", Window.size) self.shadow = True self.picking = True self.co = self.canvas self.canvas = RenderContext(compute_normal_mat=False) #self.canvas.shader.source = resource_find('./kivy3dgui/gles2.0/shaders/simple_no_light.glsl') self.canvas.shader.source = resource_find( './kivy3dgui/gles2.0/toonshader/toon.glsl') self.alpha = 0.0 self._touches = [] with self.canvas: self._translate = Translate(0, 0, 0) self._rotate = Rotate(0.0, 1.0, 0.0, 0.0) PushMatrix() self.cbs = Callback(self.setup_gl_context) if self.shadow: self.create_fbo() if self.picking: self.create_picking_fbo() self.create_motion_blur() with self.canvas.before: self.cbs = Callback(self.setup_gl_context) if self.shadow: BindTexture(texture=self.fbo.texture, index=1) BindTexture(texture=self.motion_blur_fbo.texture, index=5) PushMatrix() self.setup_scene() PopMatrix() PushMatrix() self.cc = Callback(self.check_context) PopMatrix() UpdateNormalMatrix() with self.canvas.after: self.cbr = Callback(self.reset_gl_context) PopMatrix() #Fixing Shadow and Picking self.shadow = True self.picking = True if self.shadow: self.init_fbo() if self.picking: self.init_picking() self.init_motion_blur() super(Canvas3D, self).__init__(**kwargs) Clock.schedule_interval(self.update_glsl, 1 / 60.) self._touches = {} def pitch(self, value, time): self.rotate = [value, 1.0, 0.0, 0.0] Animation.stop_all(self) Animation(rotate=[0.0, 1.0, 0.0, 0.0], duration=time).start(self) def walk(self, value, time): self.translate = [0.0, 0.0, value] Animation.stop_all(self) Animation(translate=(0.0, 0.0, 0.0), duration=time).start(self) def strafe(self, value, time): self.translate = [value, 0.0, 0.0] Animation.stop_all(self) Animation(translate=(0.0, 0.0, 0.0), duration=time).start(self) def up(self, value, time): self.translate = [0.0, value, 0.0] Animation.stop_all(self) Animation(translate=(0.0, 0.0, 0.0), duration=time).start(self) def on_translate(self, widget, value): self._translate.xyz = value[0:3] self._translate_picking.xyz = value[0:3] self._translate_motion.xyz = value[0:3] self._translate_fbo.xyz = value[0:3] def on_rotate(self, widget, value): self._rotate.set(*value) self._rotate_picking.set(*value) self._rotate_motion.set(*value) self._rotate_fbo.set(*value) def init_picking(self): with self.picking_fbo: self._translate_picking = Translate(0, 0, 0) self._rotate_picking = Rotate(0.0, 1.0, 0.0, 0.0) PushMatrix() self.setup_scene() self._picking_instruction = InstructionGroup() with self.picking_fbo.after: self.cb = Callback(self.reset_gl_context) PopMatrix() self._picking_instruction.add(Callback(self.setup_gl_context_picking)) def add_widget(self, *largs): canvas = self.canvas self.canvas = self.fbo ret = super(Canvas3D, self).add_widget(*largs) self.canvas = canvas return ret def init_fbo(self): with self.fbo: self._translate_fbo = Translate(0, 0, 0) self._rotate_fbo = Rotate(0.0, 1.0, 0.0, 0.0) PushMatrix() #ClearBuffers(clear_depth=True) self.cb = Callback(self.setup_gl_context_shadow) PushMatrix() self.setup_scene() PopMatrix() self._instruction = InstructionGroup() with self.fbo.after: self.cb = Callback(self.reset_gl_context) PopMatrix() self._instruction.add(Callback(self.setup_gl_context_shadow)) def create_picking_fbo(self): self.picking_fbo = Fbo(size=PICKING_BUFFER_SIZE, with_depthbuffer=True, compute_normal_mat=True, clear_color=(0.0, 0.0, 0.0, 0.0)) self.picking_fbo.shader.source = resource_find( './kivy3dgui/gles2.0/shaders/selection.glsl') def create_fbo(self): self.fbo = Fbo(size=PICKING_BUFFER_SIZE, with_depthbuffer=True, compute_normal_mat=True, clear_color=(1.0, 1.0, 1.0, 0.0)) self.fbo.shader.source = resource_find( './kivy3dgui/gles2.0/shaders/shadowpass.glsl') def create_motion_blur(self): self.motion_blur_fbo = Fbo(size=PICKING_BUFFER_SIZE, with_depthbuffer=True, compute_normal_mat=True, clear_color=(1.0, 1.0, 1.0, 0.0)) self.motion_blur_fbo.shader.source = resource_find( './kivy3dgui/gles2.0/shaders/dop.glsl') def init_motion_blur(self): with self.motion_blur_fbo: self._translate_motion = Translate(0, 0, 0) self._rotate_motion = Rotate(0.0, 1.0, 0.0, 0.0) PushMatrix() self.cb = Callback(self.setup_gl_context_motion_blur) PushMatrix() self.setup_scene() PopMatrix() self._instruction_motion_fbo = InstructionGroup() with self.motion_blur_fbo.after: self.cb = Callback(self.reset_gl_context) PopMatrix() self._instruction_motion_fbo.add( Callback(self.setup_gl_context_motion_blur)) def setup_gl_context(self, *args): glEnable(GL_BLEND) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glEnable(GL_DEPTH_TEST) def setup_gl_context_shadow(self, *args): self.fbo.clear_buffer() def setup_gl_context_motion_blur(self, *args): glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) self.motion_blur_fbo.clear_buffer() def setup_gl_context_picking(self, *args): glEnable(GL_DEPTH_TEST) self.picking_fbo.clear_buffer() def check_context(self, *args): pass def add_node(self, node): node.motion_id = len(self.nodes) self.adding_queue.append(node) self.nodes.append(node) with self.canvas.before: PushMatrix() Translate(bind=self._translate) node.start() PopMatrix() if self.shadow: with self.fbo: PushMatrix() node.start() PopMatrix() if self.picking: with self.picking_fbo: PushMatrix() node.start() PopMatrix() with self.motion_blur_fbo: PushMatrix() node.start() PopMatrix() node.populate_fbo(node) def reset_gl_context(self, *args): glDisable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) def update_fbo(self, time): width = self.width if self.width > 1 else 100 height = self.height if self.height > 1 else 100 asp = (width / float(height)) proj = Matrix().view_clip(-asp, asp, -1, 1, 1, 600, 1) proj = Matrix() proj.perspective(self.perspective_value, asp, 1, 1000) lightInvDir = (0.5, 2, 2) depthProjectionMatrix = Matrix().view_clip( -100 * self.shadow_threshold, 100 * self.shadow_threshold, -100 * self.shadow_threshold, 100 * self.shadow_threshold, -100 * self.shadow_threshold, 200 * self.shadow_threshold * 2, 0) depthViewMatrix = Matrix().look_at(-0.5, -50, -100, 0, 0, 0, 0, 1, 0) depthModelMatrix = Matrix().identity() depthMVP = depthProjectionMatrix.multiply(depthViewMatrix).multiply( depthModelMatrix) self.fbo['projection_mat'] = proj self.fbo['depthMVP'] = depthMVP self.fbo['diffuse_light'] = (0.0, 1.0, 0.0) self.fbo['ambient_light'] = (0.1, 0.1, 0.1) for m_pos in range(len(self.nodes)): motion_matrix = Matrix().view_clip(-asp, asp, -1, 1, 1, 600, 1) angle = self.nodes[m_pos].rotate[0] * 3.14 / 180 pos = self.nodes[m_pos].get_pos() trans = self.nodes[m_pos].translate[:] result = [0, 0, 0] result[0] = 0.3 if trans[0] < pos[0] else -0.3 result[1] = 0.3 if trans[1] < pos[1] else -0.3 result[2] = 0.3 if trans[2] < pos[2] else -0.3 motion_matrix = motion_matrix.translate(trans[0] + 0.1, trans[1] + 0.1, trans[2]) self.motion_blur_fbo['oldTransformation{0}'.format( str(m_pos))] = motion_matrix self.motion_blur_fbo['projection_mat'] = proj self.motion_blur_fbo['depthMVP'] = depthMVP if self.picking_fbo: self.picking_fbo['projection_mat'] = proj self.alpha += 10 * time self.fbo['cond'] = (0.0, 0.7) self.fbo['val_sin'] = (self.alpha, 0.0) #self.perspective_value += 0.04 def update_glsl(self, *largs): width = self.width if self.width > 1 else 100 height = self.height if self.height > 1 else 100 asp = width / float(height) proj = Matrix().view_clip(-asp, asp, -1, 1, 1, 600, 1) proj = Matrix() proj.perspective(self.perspective_value, asp, 1, 1000) matrix_camera = Matrix().identity() matrix_camera.look_at(0, 100, 300, 100, 0, -100, 0, 1, 0) self.canvas['projection_mat'] = proj self.canvas['diffuse_light'] = (0.0, 1.0, 0.0) self.canvas['ambient_light'] = (0.1, 0.1, 0.1) if self.shadow: self.canvas['texture1'] = 1 self.canvas["enabled_shadow"] = 1.0 else: self.canvas["enabled_shadow"] = 0.0 self.canvas["texture1"] = 0 depthProjectionMatrix = Matrix().view_clip( -100 * self.shadow_threshold, 100 * self.shadow_threshold, -100 * self.shadow_threshold, 100 * self.shadow_threshold, -100 * self.shadow_threshold, 200 * self.shadow_threshold * 2, 0) depthViewMatrix = Matrix().look_at(-0.5, -50, -100, 0, 0, 0, 0, 1, 0) depthModelMatrix = Matrix().identity() depthMVP = depthProjectionMatrix.multiply(depthViewMatrix).multiply( depthModelMatrix) self.canvas['depthMVP'] = depthMVP self.canvas['cond'] = (0.0, 0.7) self.canvas['val_sin'] = (self.alpha, 0.0) if self.shadow: self.update_fbo(largs[0]) #label.text = str(Clock.get_rfps()) def on_size(self, instance, value): self._update_fbo = 0 self.picking_fbo.size = PICKING_BUFFER_SIZE self.motion_blur_fbo.size = PICKING_BUFFER_SIZE def setup_scene(self): Color(1, 1, 1, 1) PushMatrix() Translate(0, 0, 0) UpdateNormalMatrix() PopMatrix() def define_rotate_angle(self, touch): x_angle = (touch.dx / self.width) * 360 y_angle = -1 * (touch.dy / self.height) * 360 return x_angle, y_angle def get_pixel_color(self, x, y): w = PICKING_BUFFER_SIZE[0] h = PICKING_BUFFER_SIZE[1] x = int(x) y = int(y) p = self.picking_fbo.pixels z = p[int(y * w * 4 + x * 4):int(y * w * 4 + x * 4 + 4)] try: z = [float(r) / 255.0 for i, r in enumerate(z)] except: z = [ord(r) / 255.0 for r in z] return z def get_fixed_points(self, x, y, move=False): _size = Window.size _size = EventLoop.window.system_size if move: _x = x / _size[0] _y = y / _size[1] return _x * PICKING_BUFFER_SIZE[0], _y * PICKING_BUFFER_SIZE[1] _x = x / _size[0] _y = y / _size[1] return _x * PICKING_BUFFER_SIZE[0], _y * PICKING_BUFFER_SIZE[1] def on_touch_down(self, touch): # transform the touch coordinate to local space x, y = self.get_fixed_points(touch.x, touch.y) pc = self.get_pixel_color(x, y) if pc == []: return False pc[1] = pc[1] pc[2] = pc[2] t_touch = copy.copy(touch) t_touch = touch t_touch.x = int(pc[1] * PICKING_BUFFER_SIZE[0]) t_touch.y = int(pc[2] * PICKING_BUFFER_SIZE[1]) self.last_touch_pos = [ t_touch.x, t_touch.y, float(t_touch.x) / float(EventLoop.window.system_size[0]), float(t_touch.x) / float(EventLoop.window.system_size[1]) ] t_touch.pos = (t_touch.x, t_touch.y) if pc[0] != 0: float_str = str(round(pc[0], 2))[0:4] if float(float_str) >= 0.50: float_str = str(round(float(float_str) - 0.50, 2))[0:4] if float_str in self.fbo_list: touch.ud["pick_value"] = float_str return self.fbo_list[float_str].on_touch_down(t_touch) return True def on_touch_move(self, touch): x, y = self.get_fixed_points(touch.x, touch.y) pc = self.get_pixel_color(x, y) if pc == []: return pc[1] = pc[1] pc[2] = pc[2] #fix if pc[0] != 0: float_str = str(round(pc[0], 2))[0:4] if float(float_str) >= 0.50: float_str = str(round(float(float_str) - 0.50, 2))[0:4] try: if touch.ud["pick_value"] != float_str: return except: pass t_touch = copy.copy(touch) t_touch = touch t_touch.x = int(pc[1] * PICKING_BUFFER_SIZE[0]) t_touch.y = int(pc[2] * PICKING_BUFFER_SIZE[1]) if 'right' in t_touch.button: t_touch.pos = (t_touch.x, t_touch.y) return t_touch.sx = float(touch.x) / float(EventLoop.window.system_size[0]) t_touch.sy = float(touch.y) / float(EventLoop.window.system_size[1]) self.last_touch_pos = [t_touch.x, t_touch.y, t_touch.sx, t_touch.sy] if pc[0] != 0: float_str = str(round(pc[0], 2))[0:4] if float(float_str) >= 0.50: float_str = str(round(float(float_str) - 0.50, 2))[0:4] if float_str in self.fbo_list: ret = self.fbo_list[float_str].dispatch( "on_touch_move", t_touch) return ret return True def on_touch_up(self, touch): x, y = self.get_fixed_points(touch.x, touch.y) pc = self.get_pixel_color(x, y) if pc == []: return pc[1] = pc[1] pc[2] = pc[2] t_touch = copy.copy(touch) t_touch = touch t_touch.x = self.last_touch_pos[0] t_touch.y = self.last_touch_pos[1] t_touch.sx = self.last_touch_pos[2] t_touch.sy = self.last_touch_pos[3] if pc[0] != 0: float_str = str(round(pc[0], 2))[0:4] if float(float_str) >= 0.50: float_str = str(round(float(float_str) - 0.50, 2))[0:4] if float_str in self.fbo_list: return self.fbo_list[float_str].on_touch_up(t_touch) pass return True
class MDCardSwipe(RelativeLayout): """ :Events: :attr:`on_swipe_complete` Called when a swipe of card is completed. """ open_progress = NumericProperty(0.0) """ Percent of visible part of side panel. The percent is specified as a floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if panel is opened. :attr:`open_progress` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.0`. """ opening_transition = StringProperty("out_cubic") """ The name of the animation transition type to use when animating to the :attr:`state` `'opened'`. :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` and defaults to `'out_cubic'`. """ closing_transition = StringProperty("out_sine") """ The name of the animation transition type to use when animating to the :attr:`state` 'closed'. :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` and defaults to `'out_sine'`. """ anchor = OptionProperty("left", options=("left", "right")) """ Anchoring screen edge for card. Available options are: `'left'`, `'right'`. :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` and defaults to `left`. """ swipe_distance = NumericProperty(50) """ The distance of the swipe with which the movement of navigation drawer begins. :attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty` and defaults to `10`. """ opening_time = NumericProperty(0.2) """ The time taken for the card to slide to the :attr:`state` `'open'`. :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.2`. """ state = OptionProperty("closed", options=("closed", "opened")) """ Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead of :attr:`status`. Available options are: `'closed'`, `'opened'`. :attr:`status` is a :class:`~kivy.properties.OptionProperty` and defaults to `'closed'`. """ max_swipe_x = NumericProperty(0.3) """ If, after the events of :attr:`~on_touch_up` card position exceeds this value - will automatically execute the method :attr:`~open_card`, and if not - will automatically be :attr:`~close_card` method. :attr:`max_swipe_x` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.3`. """ max_opened_x = NumericProperty("100dp") """ The value of the position the card shifts to when :attr:`~type_swipe` s set to `'hand'`. :attr:`max_opened_x` is a :class:`~kivy.properties.NumericProperty` and defaults to `100dp`. """ type_swipe = OptionProperty("hand", options=("auto", "hand")) """ Type of card opening when swipe. Shift the card to the edge or to a set position :attr:`~max_opened_x`. Available options are: `'auto'`, `'hand'`. :attr:`type_swipe` is a :class:`~kivy.properties.OptionProperty` and defaults to `auto`. """ _opens_process = False _to_closed = True def __init__(self, **kw): self.register_event_type("on_swipe_complete") super().__init__(**kw) def _on_swipe_complete(self, *args): self.dispatch("on_swipe_complete") def add_widget(self, widget, index=0, canvas=None): if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)): return super().add_widget(widget) def on_swipe_complete(self, *args): """Called when a swipe of card is completed.""" def on_anchor(self, instance, value): if value == "right": self.open_progress = 1.0 else: self.open_progress = 0.0 def on_open_progress(self, instance, value): if self.anchor == "left": self.children[0].x = self.width * value else: self.children[0].x = self.width * value - self.width def on_touch_move(self, touch): if self.collide_point(touch.x, touch.y): expr = (touch.x < self.swipe_distance if self.anchor == "left" else touch.x > self.width - self.swipe_distance) if expr and not self._opens_process: self._opens_process = True self._to_closed = False if self._opens_process: self.open_progress = max( min(self.open_progress + touch.dx / self.width, 2.5), 0) return super().on_touch_move(touch) def on_touch_up(self, touch): if self.collide_point(touch.x, touch.y): if not self._to_closed: self._opens_process = False self.complete_swipe() return super().on_touch_up(touch) def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): if self.state == "opened": self._to_closed = True self.close_card() return super().on_touch_down(touch) def complete_swipe(self): expr = (self.open_progress <= self.max_swipe_x if self.anchor == "left" else self.open_progress >= self.max_swipe_x) if expr: self.close_card() else: self.open_card() def open_card(self): if self.type_swipe == "hand": swipe_x = (self.max_opened_x if self.anchor == "left" else -self.max_opened_x) else: swipe_x = self.width if self.anchor == "left" else 0 anim = Animation(x=swipe_x, t=self.opening_transition, d=self.opening_time) anim.bind(on_complete=self._on_swipe_complete) anim.start(self.children[0]) self.state = "opened" def close_card(self): anim = Animation(x=0, t=self.closing_transition, d=self.opening_time) anim.bind(on_complete=self._reset_open_progress) anim.start(self.children[0]) self.state = "closed" def _reset_open_progress(self, *args): self.open_progress = 0.0 if self.anchor == "left" else 1.0 self._to_closed = False self.dispatch("on_swipe_complete")
class GridLayout(Layout): '''Grid layout class. See module documentation for more information. ''' spacing = VariableListProperty([0, 0], length=2) '''Spacing between children: [spacing_horizontal, spacing_vertical]. spacing also accepts a one argument form [spacing]. :attr:`spacing` is a :class:`~kivy.properties.VariableListProperty` and defaults to [0, 0]. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between the layout box and its children: [padding_left, padding_top, padding_right, padding_bottom]. padding also accepts a two argument form [padding_horizontal, padding_vertical] and a one argument form [padding]. .. versionchanged:: 1.7.0 Replaced NumericProperty with VariableListProperty. :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and defaults to [0, 0, 0, 0]. ''' cols = BoundedNumericProperty(None, min=0, allownone=True) '''Number of columns in the grid. .. versionchanged:: 1.0.8 Changed from a NumericProperty to BoundedNumericProperty. You can no longer set this to a negative value. :attr:`cols` is a :class:`~kivy.properties.NumericProperty` and defaults to None. ''' rows = BoundedNumericProperty(None, min=0, allownone=True) '''Number of rows in the grid. .. versionchanged:: 1.0.8 Changed from a NumericProperty to a BoundedNumericProperty. You can no longer set this to a negative value. :attr:`rows` is a :class:`~kivy.properties.NumericProperty` and defaults to None. ''' col_default_width = NumericProperty(0) '''Default minimum size to use for a column. .. versionadded:: 1.0.7 :attr:`col_default_width` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' row_default_height = NumericProperty(0) '''Default minimum size to use for row. .. versionadded:: 1.0.7 :attr:`row_default_height` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' col_force_default = BooleanProperty(False) '''If True, ignore the width and size_hint_x of the child and use the default column width. .. versionadded:: 1.0.7 :attr:`col_force_default` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' row_force_default = BooleanProperty(False) '''If True, ignore the height and size_hint_y of the child and use the default row height. .. versionadded:: 1.0.7 :attr:`row_force_default` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' cols_minimum = DictProperty({}) '''Dict of minimum width for each column. The dictionary keys are the column numbers, e.g. 0, 1, 2... .. versionadded:: 1.0.7 :attr:`cols_minimum` is a :class:`~kivy.properties.DictProperty` and defaults to {}. ''' rows_minimum = DictProperty({}) '''Dict of minimum height for each row. The dictionary keys are the row numbers, e.g. 0, 1, 2... .. versionadded:: 1.0.7 :attr:`rows_minimum` is a :class:`~kivy.properties.DictProperty` and defaults to {}. ''' minimum_width = NumericProperty(0) '''Automatically computed minimum width needed to contain all children. .. versionadded:: 1.0.8 :attr:`minimum_width` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. It is read only. ''' minimum_height = NumericProperty(0) '''Automatically computed minimum height needed to contain all children. .. versionadded:: 1.0.8 :attr:`minimum_height` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. It is read only. ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''Automatically computed minimum size needed to contain all children. .. versionadded:: 1.0.8 :attr:`minimum_size` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`minimum_width`, :attr:`minimum_height`) properties. It is read only. ''' orientation = OptionProperty('lr-tb', options=('lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt', 'bt-rl')) '''Orientation of the layout. :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and defaults to 'lr-tb'. Valid orientations are 'lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt' and 'bt-rl'. .. versionadded:: 2.0.0 .. note:: 'lr' means Left to Right. 'rl' means Right to Left. 'tb' means Top to Bottom. 'bt' means Bottom to Top. ''' def __init__(self, **kwargs): self._cols = self._rows = None super(GridLayout, self).__init__(**kwargs) fbind = self.fbind update = self._trigger_layout fbind('col_default_width', update) fbind('row_default_height', update) fbind('col_force_default', update) fbind('row_force_default', update) fbind('cols', update) fbind('rows', update) fbind('parent', update) fbind('spacing', update) fbind('padding', update) fbind('children', update) fbind('size', update) fbind('pos', update) fbind('orientation', update) def get_max_widgets(self): if self.cols and self.rows: return self.rows * self.cols else: return None def on_children(self, instance, value): # if that makes impossible to construct things with deffered method, # migrate this test in do_layout, and/or issue a warning. smax = self.get_max_widgets() if smax and len(value) > smax: raise GridLayoutException( 'Too many children in GridLayout. Increase rows/cols!') @property def _fills_row_first(self): return self.orientation[0] in 'lr' @property def _fills_from_left_to_right(self): return 'lr' in self.orientation @property def _fills_from_top_to_bottom(self): return 'tb' in self.orientation def _init_rows_cols_sizes(self, count): # the goal here is to calculate the minimum size of every cols/rows # and determine if they have stretch or not current_cols = self.cols current_rows = self.rows # if no cols or rows are set, we can't calculate minimum size. # the grid must be contrained at least on one side if not current_cols and not current_rows: Logger.warning('%r have no cols or rows set, ' 'layout is not triggered.' % self) return if current_cols is None: current_cols = int(ceil(count / float(current_rows))) elif current_rows is None: current_rows = int(ceil(count / float(current_cols))) current_cols = max(1, current_cols) current_rows = max(1, current_rows) self._has_hint_bound_x = False self._has_hint_bound_y = False self._cols_min_size_none = 0. # min size from all the None hint self._rows_min_size_none = 0. # min size from all the None hint self._cols = cols = [self.col_default_width] * current_cols self._cols_sh = [None] * current_cols self._cols_sh_min = [None] * current_cols self._cols_sh_max = [None] * current_cols self._rows = rows = [self.row_default_height] * current_rows self._rows_sh = [None] * current_rows self._rows_sh_min = [None] * current_rows self._rows_sh_max = [None] * current_rows # update minimum size from the dicts items = (i for i in self.cols_minimum.items() if i[0] < len(cols)) for index, value in items: cols[index] = max(value, cols[index]) items = (i for i in self.rows_minimum.items() if i[0] < len(rows)) for index, value in items: rows[index] = max(value, rows[index]) return True def _fill_rows_cols_sizes(self): cols, rows = self._cols, self._rows cols_sh, rows_sh = self._cols_sh, self._rows_sh cols_sh_min, rows_sh_min = self._cols_sh_min, self._rows_sh_min cols_sh_max, rows_sh_max = self._cols_sh_max, self._rows_sh_max # calculate minimum size for each columns and rows has_bound_y = has_bound_x = False idx_iter = self._create_idx_iter(len(cols), len(rows)) for child, (col, row) in zip(reversed(self.children), idx_iter): (shw, shh), (w, h) = child.size_hint, child.size shw_min, shh_min = child.size_hint_min shw_max, shh_max = child.size_hint_max # compute minimum size / maximum stretch needed if shw is None: cols[col] = nmax(cols[col], w) else: cols_sh[col] = nmax(cols_sh[col], shw) if shw_min is not None: has_bound_x = True cols_sh_min[col] = nmax(cols_sh_min[col], shw_min) if shw_max is not None: has_bound_x = True cols_sh_max[col] = nmin(cols_sh_max[col], shw_max) if shh is None: rows[row] = nmax(rows[row], h) else: rows_sh[row] = nmax(rows_sh[row], shh) if shh_min is not None: has_bound_y = True rows_sh_min[row] = nmax(rows_sh_min[row], shh_min) if shh_max is not None: has_bound_y = True rows_sh_max[row] = nmin(rows_sh_max[row], shh_max) self._has_hint_bound_x = has_bound_x self._has_hint_bound_y = has_bound_y def _update_minimum_size(self): # calculate minimum width/height needed, starting from padding + # spacing l, t, r, b = self.padding spacing_x, spacing_y = self.spacing cols, rows = self._cols, self._rows width = l + r + spacing_x * (len(cols) - 1) self._cols_min_size_none = sum(cols) + width # we need to subtract for the sh_max/min the already guaranteed size # due to having a None in the col. So sh_min gets smaller by that size # since it's already covered. Similarly for sh_max, because if we # already exceeded the max, the subtracted max will be zero, so # it won't get larger if self._has_hint_bound_x: cols_sh_min = self._cols_sh_min cols_sh_max = self._cols_sh_max for i, (c, sh_min, sh_max) in enumerate(zip(cols, cols_sh_min, cols_sh_max)): if sh_min is not None: width += max(c, sh_min) cols_sh_min[i] = max(0., sh_min - c) else: width += c if sh_max is not None: cols_sh_max[i] = max(0., sh_max - c) else: width = self._cols_min_size_none height = t + b + spacing_y * (len(rows) - 1) self._rows_min_size_none = sum(rows) + height if self._has_hint_bound_y: rows_sh_min = self._rows_sh_min rows_sh_max = self._rows_sh_max for i, (r, sh_min, sh_max) in enumerate(zip(rows, rows_sh_min, rows_sh_max)): if sh_min is not None: height += max(r, sh_min) rows_sh_min[i] = max(0., sh_min - r) else: height += r if sh_max is not None: rows_sh_max[i] = max(0., sh_max - r) else: height = self._rows_min_size_none # finally, set the minimum size self.minimum_size = (width, height) def _finalize_rows_cols_sizes(self): selfw = self.width selfh = self.height # resolve size for each column if self.col_force_default: cols = [self.col_default_width] * len(self._cols) for index, value in self.cols_minimum.items(): cols[index] = value self._cols = cols else: cols = self._cols cols_sh = self._cols_sh cols_sh_min = self._cols_sh_min cols_weight = float(sum((x for x in cols_sh if x is not None))) stretch_w = max(0., selfw - self._cols_min_size_none) if stretch_w > 1e-9: if self._has_hint_bound_x: # fix the hints to be within bounds self.layout_hint_with_bounds( cols_weight, stretch_w, sum((c for c in cols_sh_min if c is not None)), cols_sh_min, self._cols_sh_max, cols_sh) for index, col_stretch in enumerate(cols_sh): # if the col don't have stretch information, nothing to do if not col_stretch: continue # add to the min width whatever remains from size_hint cols[index] += stretch_w * col_stretch / cols_weight # same algo for rows if self.row_force_default: rows = [self.row_default_height] * len(self._rows) for index, value in self.rows_minimum.items(): rows[index] = value self._rows = rows else: rows = self._rows rows_sh = self._rows_sh rows_sh_min = self._rows_sh_min rows_weight = float(sum((x for x in rows_sh if x is not None))) stretch_h = max(0., selfh - self._rows_min_size_none) if stretch_h > 1e-9: if self._has_hint_bound_y: # fix the hints to be within bounds self.layout_hint_with_bounds( rows_weight, stretch_h, sum((r for r in rows_sh_min if r is not None)), rows_sh_min, self._rows_sh_max, rows_sh) for index, row_stretch in enumerate(rows_sh): # if the row don't have stretch information, nothing to do if not row_stretch: continue # add to the min height whatever remains from size_hint rows[index] += stretch_h * row_stretch / rows_weight def _iterate_layout(self, count): orientation = self.orientation padding = self.padding spacing_x, spacing_y = self.spacing cols = self._cols if self._fills_from_left_to_right: x_iter = accumulate( chain( (self.x + padding[0], ), (col_width + spacing_x for col_width in islice(cols, len(cols) - 1)), )) else: x_iter = accumulate( chain( (self.right - padding[2] - cols[-1], ), (col_width + spacing_x for col_width in islice(reversed(cols), 1, None)), ), sub) cols = reversed(cols) rows = self._rows if self._fills_from_top_to_bottom: y_iter = accumulate( chain( (self.top - padding[1] - rows[0], ), (row_height + spacing_y for row_height in islice(rows, 1, None)), ), sub) else: y_iter = accumulate( chain( (self.y + padding[3], ), (row_height + spacing_y for row_height in islice(reversed(rows), len(rows) - 1)), )) rows = reversed(rows) if self._fills_row_first: for i, (y, x), (row_height, col_width) in zip(reversed(range(count)), product(y_iter, x_iter), product(rows, cols)): yield i, x, y, col_width, row_height else: for i, (x, y), (col_width, row_height) in zip(reversed(range(count)), product(x_iter, y_iter), product(cols, rows)): yield i, x, y, col_width, row_height def do_layout(self, *largs): children = self.children if not children or not self._init_rows_cols_sizes(len(children)): l, t, r, b = self.padding self.minimum_size = l + r, t + b return self._fill_rows_cols_sizes() self._update_minimum_size() self._finalize_rows_cols_sizes() for i, x, y, w, h in self._iterate_layout(len(children)): c = children[i] c.pos = x, y shw, shh = c.size_hint shw_min, shh_min = c.size_hint_min shw_max, shh_max = c.size_hint_max if shw_min is not None: if shw_max is not None: w = max(min(w, shw_max), shw_min) else: w = max(w, shw_min) else: if shw_max is not None: w = min(w, shw_max) if shh_min is not None: if shh_max is not None: h = max(min(h, shh_max), shh_min) else: h = max(h, shh_min) else: if shh_max is not None: h = min(h, shh_max) if shw is None: if shh is not None: c.height = h else: if shh is None: c.width = w else: c.size = (w, h) def _create_idx_iter(self, n_cols, n_rows): col_indices = range(n_cols) if self._fills_from_left_to_right \ else range(n_cols - 1, -1, -1) row_indices = range(n_rows) if self._fills_from_top_to_bottom \ else range(n_rows - 1, -1, -1) if self._fills_row_first: return ( (col_index, row_index) for row_index, col_index in product(row_indices, col_indices)) else: return product(col_indices, row_indices)
class TransitionBase(EventDispatcher): '''TransitionBase is used to animate 2 screens within the :class:`ScreenManager`. This class acts as a base for other implementations like the :class:`SlideTransition` and :class:`SwapTransition`. :Events: `on_progress`: Transition object, progression float Fired during the animation of the transition. `on_complete`: Transition object Fired when the transition is fininshed. ''' screen_out = ObjectProperty() '''Property that contains the screen to hide. Automatically set by the :class:`ScreenManager`. :class:`screen_out` is an :class:`~kivy.properties.ObjectProperty` and defaults to None. ''' screen_in = ObjectProperty() '''Property that contains the screen to show. Automatically set by the :class:`ScreenManager`. :class:`screen_in` is an :class:`~kivy.properties.ObjectProperty` and defaults to None. ''' duration = NumericProperty(.4) '''Duration in seconds of the transition. :class:`duration` is a :class:`~kivy.properties.NumericProperty` and defaults to .4 (= 400ms). .. versionchanged:: 1.8.0 Default duration has been changed from 700ms to 400ms. ''' manager = ObjectProperty() ''':class:`ScreenManager` object, set when the screen is added to a manager. :attr:`manager` is an :class:`~kivy.properties.ObjectProperty` and defaults to None, read-only. ''' is_active = BooleanProperty(False) '''Indicate whether the transition is currently active or not. :attr:`is_active` is a :class:`~kivy.properties.BooleanProperty` and defaults to False, read-only. ''' # privates _anim = ObjectProperty(allownone=True) __events__ = ('on_progress', 'on_complete') def start(self, manager): '''(internal) Starts the transition. This is automatically called by the :class:`ScreenManager`. ''' if self.is_active: raise ScreenManagerException('start() is called twice!') self.manager = manager self._anim = Animation(d=self.duration, s=0) self._anim.bind(on_progress=self._on_progress, on_complete=self._on_complete) self.add_screen(self.screen_in) self.screen_in.transition_progress = 0. self.screen_in.transition_state = 'in' self.screen_out.transition_progress = 0. self.screen_out.transition_state = 'out' self.screen_in.dispatch('on_pre_enter') self.screen_out.dispatch('on_pre_leave') self.is_active = True self._anim.start(self) self.dispatch('on_progress', 0) def stop(self): '''(internal) Stops the transition. This is automatically called by the :class:`ScreenManager`. ''' if self._anim: self._anim.cancel(self) self.dispatch('on_complete') self._anim = None self.is_active = False def add_screen(self, screen): '''(internal) Used to add a screen to the :class:`ScreenManager`. ''' self.manager.real_add_widget(screen) def remove_screen(self, screen): '''(internal) Used to remove a screen from the :class:`ScreenManager`. ''' self.manager.real_remove_widget(screen) def on_complete(self): self.remove_screen(self.screen_out) def on_progress(self, progression): pass def _on_progress(self, *l): progress = l[-1] self.screen_in.transition_progress = progress self.screen_out.transition_progress = 1. - progress self.dispatch('on_progress', progress) def _on_complete(self, *l): self.is_active = False self.dispatch('on_complete') self.screen_in.dispatch('on_enter') self.screen_out.dispatch('on_leave') self._anim = None
class Screen(RelativeLayout): '''Screen is an element intended to be used with a :class:`ScreenManager`. Check module documentation for more information. :Events: `on_pre_enter`: () Event fired when the screen is about to be used: the entering animation is started. `on_enter`: () Event fired when the screen is displayed: the entering animation is complete. `on_pre_leave`: () Event fired when the screen is about to be removed: the leaving animation is started. `on_leave`: () Event fired when the screen is removed: the leaving animation is finished. .. versionchanged:: 1.6.0 Events `on_pre_enter`, `on_enter`, `on_pre_leave` and `on_leave` were added. ''' name = StringProperty('') ''' Name of the screen which must be unique within a :class:`ScreenManager`. This is the name used for :attr:`ScreenManager.current`. :attr:`name` is a :class:`~kivy.properties.StringProperty` and defaults to ''. ''' manager = ObjectProperty(None, allownone=True) ''':class:`ScreenManager` object, set when the screen is added to a manager. :attr:`manager` is an :class:`~kivy.properties.ObjectProperty` and defaults to None, read-only. ''' transition_progress = NumericProperty(0.) '''Value that represents the completion of the current transition, if any is occuring. If a transition is in progress, whatever the mode, the value will change from 0 to 1. If you want to know if it's an entering or leaving animation, check the :attr:`transition_state`. :attr:`transition_progress` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' transition_state = OptionProperty('out', options=('in', 'out')) '''Value that represents the state of the transition: - 'in' if the transition is going to show your screen - 'out' if the transition is going to hide your screen After the transition is complete, the state will retain it's last value (in or out). :attr:`transition_state` is an :class:`~kivy.properties.OptionProperty` and defaults to 'out'. ''' __events__ = ('on_pre_enter', 'on_enter', 'on_pre_leave', 'on_leave') def on_pre_enter(self, *args): pass def on_enter(self, *args): pass def on_pre_leave(self, *args): pass def on_leave(self, *args): pass def __repr__(self): return '<Screen name=%r>' % self.name
class MEAArrayAlign(Scatter): num_rows = NumericProperty(12) num_cols = NumericProperty(12) pitch = NumericProperty(20) diameter = NumericProperty(3) show = BooleanProperty(False) color = None label = None label2 = None def __init__(self, **kwargs): super(MEAArrayAlign, self).__init__(**kwargs) label = self.label = Factory.XYSizedLabel(text='A1') self.add_widget(label) label2 = self.label2 = Factory.XYSizedLabel(text='M1') self.add_widget(label2) self.fbind('num_rows', self.update_graphics) self.fbind('num_cols', self.update_graphics) self.fbind('pitch', self.update_graphics) self.fbind('diameter', self.update_graphics) self.update_graphics() def track_show(*largs): label.color = 1, 1, 1, (1 if self.show else 0) label2.color = 1, 1, 1, (1 if self.show else 0) self.fbind('show', track_show) track_show() def update_graphics(self, *largs): self.canvas.remove_group('MEAArrayAlign') pitch = self.pitch radius = self.diameter / 2.0 with self.canvas: self.color = Color(1, 1, 1, 1 if self.show else 0, group='MEAArrayAlign') for row in range(self.num_rows): for col in range(self.num_cols): Point(points=[col * pitch, row * pitch], pointsize=radius, group='MEAArrayAlign') h = max((self.num_rows - 1) * pitch, 0) w = max((self.num_cols - 1) * pitch, 0) self.label.y = h self.label2.y = 0 self.label2.right = self.label.right = w self.size = w, h + 35 def on_touch_down(self, touch): if not self.do_translation_x and \ not self.do_translation_y and \ not self.do_rotation and \ not self.do_scale: return False return super(MEAArrayAlign, self).on_touch_down(touch) def on_touch_move(self, touch): if not self.do_translation_x and \ not self.do_translation_y and \ not self.do_rotation and \ not self.do_scale: return False return super(MEAArrayAlign, self).on_touch_move(touch) def on_touch_up(self, touch): if not self.do_translation_x and \ not self.do_translation_y and \ not self.do_rotation and \ not self.do_scale: return False return super(MEAArrayAlign, self).on_touch_up(touch) @staticmethod def make_matrix(elems): mat = Matrix() mat.set(array=elems) return mat @staticmethod def compare_mat(mat, mat_list): return mat.tolist() == tuple(tuple(item) for item in mat_list)
class Car(Widget): angle = NumericProperty(0) rotation = NumericProperty(0) velocity_x = NumericProperty(0) velocity_y = NumericProperty(0) velocity = ReferenceListProperty(velocity_x, velocity_y) sensor1_x = NumericProperty(0) sensor1_y = NumericProperty(0) sensor1 = ReferenceListProperty(sensor1_x, sensor1_y) sensor2_x = NumericProperty(0) sensor2_y = NumericProperty(0) sensor2 = ReferenceListProperty(sensor2_x, sensor2_y) sensor3_x = NumericProperty(0) sensor3_y = NumericProperty(0) sensor3 = ReferenceListProperty(sensor3_x, sensor3_y) signal1 = NumericProperty(0) signal2 = NumericProperty(0) signal3 = NumericProperty(0) def move(self, rotation): self.pos = Vector(*self.velocity) + self.pos self.rotation = rotation self.angle = self.angle + self.rotation self.sensor1 = Vector(30, 0).rotate(self.angle) + self.pos self.sensor2 = Vector(30, 0).rotate((self.angle+30)%360) + self.pos self.sensor3 = Vector(30, 0).rotate((self.angle-30)%360) + self.pos self.signal1 = int(np.sum(sand[int(self.sensor1_x)-10:int(self.sensor1_x)+10, int(self.sensor1_y)-10:int(self.sensor1_y)+10]))/400. self.signal2 = int(np.sum(sand[int(self.sensor2_x)-10:int(self.sensor2_x)+10, int(self.sensor2_y)-10:int(self.sensor2_y)+10]))/400. self.signal3 = int(np.sum(sand[int(self.sensor3_x)-10:int(self.sensor3_x)+10, int(self.sensor3_y)-10:int(self.sensor3_y)+10]))/400. if self.sensor1_x>longueur-10 or self.sensor1_x<10 or self.sensor1_y>largeur-10 or self.sensor1_y<10: self.signal1 = 1. if self.sensor2_x>longueur-10 or self.sensor2_x<10 or self.sensor2_y>largeur-10 or self.sensor2_y<10: self.signal2 = 1. if self.sensor3_x>longueur-10 or self.sensor3_x<10 or self.sensor3_y>largeur-10 or self.sensor3_y<10: self.signal3 = 1.
class CodePlace(BoxLayout): code_manager = ObjectProperty(None) tab_manager = ObjectProperty(None) new_empty_tab = NumericProperty(0) '''count of empty tabs that has been opened ''' def __init__(self, **kwargs): super(CodePlace, self).__init__(**kwargs) self.code_manager = CodeScreenManager() self.add_widget(self.code_manager) Window.bind(on_key_down=self.keyboard_down) Window.bind(on_dropfile=self.file_droped) def file_droped(self, window, filename, *args): if self.collide_point(*window.mouse_pos): print('File droped on code input') if filename: self.add_code_tab(filename=filename) def add_widget(self, widget, tab_type=''): if len(self.children) > 1: if tab_type == 'code' or tab_type == 'new_file': tab = TabToggleButton(text=os.path.split(widget.filename)[1], filename=widget.filename) widget.tab = tab widget.tab_type = tab_type self.code_manager.add_widget(widget, widget.filename, tab_type=tab_type) elif tab_type == 'welcome': self.code_manager.add_widget(widget, 'kivystudiowelcome', tab_type=tab_type) tab = TabToggleButton(text='Welcome', filename='kivystudiowelcome') tab.bind(state=self.change_screen) self.tab_manager.add_widget(tab) Clock.schedule_once(lambda dt: setattr(tab, 'state', 'down')) else: super(CodePlace, self).add_widget(widget) def change_screen(self, tab, state): if state == 'down': self.code_manager.current = tab.filename checked_list = list( filter(lambda child: child != tab, ToggleButtonBehavior.get_widgets(tab.group))) for child in checked_list: if child != tab: child.state = 'normal' def keyboard_down(self, window, *args): if args[0] == 9 and args[3] == ['ctrl' ]: # switching screen with ctrl tab self.code_manager.current = self.code_manager.next() return True if args[0] == 119 and args[3] == ['ctrl']: # close tab name = self.code_manager.get_screen(self.code_manager.current).name tab = get_tab_from_group(name) self.remove_code_tab(tab) def remove_code_tab(self, tab): self.tab_manager.remove_widget(tab) codeinput = self.code_manager.get_children_with_filename(tab.filename) self.code_manager.remove_widget(codeinput) if tab.filename.startswith('Untitled-') and not os.path.exists( tab.filename): self.new_empty_tab -= 1 def add_code_tab(self, filename='', tab_type='code'): if filename: try: self.code_manager.get_screen(filename) except ScreenManagerException: # then it is not added self.add_widget(FullCodeInput(filename=filename), tab_type=tab_type) elif tab_type == 'new_file': # a new tab self.new_empty_tab += 1 while True: try: self.code_manager.get_screen(filename) except ScreenManagerException: # then it is not added filename = 'Untitled-{}'.format(self.new_empty_tab) self.add_widget(FullCodeInput(filename=filename), tab_type=tab_type) return self.new_empty_tab += 1 elif tab_type == 'welcome': try: self.code_manager.get_screen('kivystudiowelcome') except ScreenManagerException: # then it is not added self.add_widget(WelcomeTab(), tab_type=tab_type)
class BaseListItem(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, FloatLayout): '''Base class to all ListItems. Not supposed to be instantiated on its own. ''' text = StringProperty() '''Text shown in the first line. :attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults to "". ''' text_color = ListProperty(None) ''' Text color used if theme_text_color is set to 'Custom' ''' font_style = OptionProperty('Subhead', options=[ 'Body1', 'Body2', 'Caption', 'Subhead', 'Title', 'Headline', 'Display1', 'Display2', 'Display3', 'Display4', 'Button', 'Icon' ]) theme_text_color = StringProperty('Primary', allownone=True) ''' Theme text color for primary text ''' secondary_text = StringProperty() '''Text shown in the second and potentially third line. The text will wrap into the third line if the ListItem's type is set to \'one-line\'. It can be forced into the third line by adding a \\n escape sequence. :attr:`secondary_text` is a :class:`~kivy.properties.StringProperty` and defaults to "". ''' secondary_text_color = ListProperty(None) ''' Text color used for secondary text if secondary_theme_text_color is set to 'Custom' ''' secondary_theme_text_color = StringProperty('Secondary', allownone=True) ''' Theme text color for secondary primary text ''' secondary_font_style = OptionProperty('Body1', options=[ 'Body1', 'Body2', 'Caption', 'Subhead', 'Title', 'Headline', 'Display1', 'Display2', 'Display3', 'Display4', 'Button', 'Icon' ]) divider = OptionProperty('Full', options=['Full', 'Inset', None], allownone=True) _txt_left_pad = NumericProperty(dp(16)) _txt_top_pad = NumericProperty() _txt_bot_pad = NumericProperty() _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS) _num_lines = 2
class PlayerStats(BoxLayout): name = StringProperty('') money = NumericProperty(0)
class ThreeLineIconListItem(ContainerSupport, ThreeLineListItem): _txt_left_pad = NumericProperty(dp(72))
class SlugImage(RelativeLayout): body_image = StringProperty('') eye_image = StringProperty('') y_position = NumericProperty(0)
class ThreeLineRightIconListItem(ContainerSupport, ThreeLineListItem): # dp(40) = dp(16) + dp(24): _txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
class Graph(Widget): '''Graph class, see module documentation for more information. ''' # triggers a full reload of graphics _trigger = ObjectProperty(None) # triggers only a repositioning of objects due to size/pos updates _trigger_size = ObjectProperty(None) # triggers only a update of colors, e.g. tick_color _trigger_color = ObjectProperty(None) # holds widget with the x-axis label _xlabel = ObjectProperty(None) # holds widget with the y-axis label _ylabel = ObjectProperty(None) # holds all the x-axis tick mark labels _x_grid_label = ListProperty([]) # holds all the y-axis tick mark labels _y_grid_label = ListProperty([]) # the mesh drawing all the ticks/grids _mesh_ticks = ObjectProperty(None) # the mesh which draws the surrounding rectangle _mesh_rect = ObjectProperty(None) # a list of locations of major and minor ticks. The values are not # but is in the axis min - max range _ticks_majorx = ListProperty([]) _ticks_minorx = ListProperty([]) _ticks_majory = ListProperty([]) _ticks_minory = ListProperty([]) tick_color = ListProperty([.25, .25, .25, 1]) '''Color of the grid/ticks, default to 1/4. grey. ''' background_color = ListProperty([0, 0, 0, 0]) '''Color of the background, defaults to transparent ''' border_color = ListProperty([1, 1, 1, 1]) '''Color of the border, defaults to white ''' label_options = DictProperty() '''Label options that will be passed to `:class:`kivy.uix.Label`. ''' _with_stencilbuffer = BooleanProperty(True) '''Whether :class:`Graph`'s FBO should use FrameBuffer (True) or not (False). .. warning:: This property is internal and so should be used with care. It can break some other graphic instructions used by the :class:`Graph`, for example you can have problems when drawing :class:`SmoothLinePlot` plots, so use it only when you know what exactly you are doing. :data:`_with_stencilbuffer` is a :class:`~kivy.properties.BooleanProperty`, defaults to True. ''' def __init__(self, **kwargs): super(Graph, self).__init__(**kwargs) with self.canvas: self._fbo = Fbo(size=self.size, with_stencilbuffer=self._with_stencilbuffer) with self._fbo: self._background_color = Color(*self.background_color) self._background_rect = Rectangle(size=self.size) self._mesh_ticks_color = Color(*self.tick_color) self._mesh_ticks = Mesh(mode='lines') self._mesh_rect_color = Color(*self.border_color) self._mesh_rect = Mesh(mode='line_strip') with self.canvas: Color(1, 1, 1) self._fbo_rect = Rectangle(size=self.size, texture=self._fbo.texture) mesh = self._mesh_rect mesh.vertices = [0] * (5 * 4) mesh.indices = range(5) self._plot_area = StencilView() self.add_widget(self._plot_area) t = self._trigger = Clock.create_trigger(self._redraw_all) ts = self._trigger_size = Clock.create_trigger(self._redraw_size) tc = self._trigger_color = Clock.create_trigger(self._update_colors) self.bind(center=ts, padding=ts, precision=ts, plots=ts, x_grid=ts, y_grid=ts, draw_border=ts) self.bind(xmin=t, xmax=t, xlog=t, x_ticks_major=t, x_ticks_minor=t, xlabel=t, x_grid_label=t, ymin=t, ymax=t, ylog=t, y_ticks_major=t, y_ticks_minor=t, ylabel=t, y_grid_label=t, font_size=t, label_options=t, x_ticks_angle=t) self.bind(tick_color=tc, background_color=tc, border_color=tc) self._trigger() def add_widget(self, widget): if widget is self._plot_area: canvas = self.canvas self.canvas = self._fbo super(Graph, self).add_widget(widget) if widget is self._plot_area: self.canvas = canvas def remove_widget(self, widget): if widget is self._plot_area: canvas = self.canvas self.canvas = self._fbo super(Graph, self).remove_widget(widget) if widget is self._plot_area: self.canvas = canvas def _get_ticks(self, major, minor, log, s_min, s_max): if major and s_max > s_min: if log: s_min = log10(s_min) s_max = log10(s_max) # count the decades in min - max. This is in actual decades, # not logs. n_decades = floor(s_max - s_min) # for the fractional part of the last decade, we need to # convert the log value, x, to 10**x but need to handle # differently if the last incomplete decade has a decade # boundary in it if floor(s_min + n_decades) != floor(s_max): n_decades += 1 - (10**(s_min + n_decades + 1) - 10**s_max) / 10**floor(s_max + 1) else: n_decades += ((10**s_max - 10**(s_min + n_decades)) / 10**floor(s_max + 1)) # this might be larger than what is needed, but we delete # excess later n_ticks_major = n_decades / float(major) n_ticks = int( floor(n_ticks_major * (minor if minor >= 1. else 1.0))) + 2 # in decade multiples, e.g. 0.1 of the decade, the distance # between ticks decade_dist = major / float(minor if minor else 1.0) points_minor = [0] * n_ticks points_major = [0] * n_ticks k = 0 # position in points major k2 = 0 # position in points minor # because each decade is missing 0.1 of the decade, if a tick # falls in < min_pos skip it min_pos = 0.1 - 0.00001 * decade_dist s_min_low = floor(s_min) # first real tick location. value is in fractions of decades # from the start we have to use decimals here, otherwise # floating point inaccuracies results in bad values start_dec = ceil((10**Decimal(s_min - s_min_low - 1)) / Decimal(decade_dist)) * decade_dist count_min = (0 if not minor else floor(start_dec / decade_dist) % minor) start_dec += s_min_low count = 0 # number of ticks we currently have passed start while True: # this is the current position in decade that we are. # e.g. -0.9 means that we're at 0.1 of the 10**ceil(-0.9) # decade pos_dec = start_dec + decade_dist * count pos_dec_low = floor(pos_dec) diff = pos_dec - pos_dec_low zero = abs(diff) < 0.001 * decade_dist if zero: # the same value as pos_dec but in log scale pos_log = pos_dec_low else: pos_log = log10( (pos_dec - pos_dec_low) * 10**ceil(pos_dec)) if pos_log > s_max: break count += 1 if zero or diff >= min_pos: if minor and not count_min % minor: points_major[k] = pos_log k += 1 else: points_minor[k2] = pos_log k2 += 1 count_min += 1 else: # distance between each tick tick_dist = major / float(minor if minor else 1.0) n_ticks = int(floor((s_max - s_min) / tick_dist) + 1) points_major = [ 0 ] * int(floor((s_max - s_min) / float(major)) + 1) points_minor = [0] * (n_ticks - len(points_major) + 1) k = 0 # position in points major k2 = 0 # position in points minor for m in range(0, n_ticks): if minor and m % minor: points_minor[k2] = m * tick_dist + s_min k2 += 1 else: points_major[k] = m * tick_dist + s_min k += 1 del points_major[k:] del points_minor[k2:] else: points_major = [] points_minor = [] return points_major, points_minor def _update_labels(self): xlabel = self._xlabel ylabel = self._ylabel x = self.x y = self.y width = self.width height = self.height padding = self.padding x_next = padding + x y_next = padding + y xextent = width + x yextent = height + y ymin = self.ymin ymax = self.ymax xmin = self.xmin precision = self.precision x_overlap = False y_overlap = False # set up x and y axis labels if xlabel: xlabel.text = self.xlabel xlabel.texture_update() xlabel.size = xlabel.texture_size xlabel.pos = int(x + width / 2. - xlabel.width / 2.), int(padding + y) y_next += padding + xlabel.height if ylabel: ylabel.text = self.ylabel ylabel.texture_update() ylabel.size = ylabel.texture_size ylabel.x = padding + x - (ylabel.width / 2. - ylabel.height / 2.) x_next += padding + ylabel.height xpoints = self._ticks_majorx xlabels = self._x_grid_label xlabel_grid = self.x_grid_label ylabel_grid = self.y_grid_label ypoints = self._ticks_majory ylabels = self._y_grid_label # now x and y tick mark labels if len(ylabels) and ylabel_grid: # horizontal size of the largest tick label, to have enough room funcexp = exp10 if self.ylog else identity funclog = log10 if self.ylog else identity ylabels[0].text = precision % funcexp(ypoints[0]) ylabels[0].texture_update() y1 = ylabels[0].texture_size y_start = y_next + (padding + y1[1] if len(xlabels) and xlabel_grid else 0) + \ (padding + y1[1] if not y_next else 0) yextent = y + height - padding - y1[1] / 2. ymin = funclog(ymin) ratio = (yextent - y_start) / float(funclog(ymax) - ymin) y_start -= y1[1] / 2. y1 = y1[0] for k in range(len(ylabels)): ylabels[k].text = precision % funcexp(ypoints[k]) ylabels[k].texture_update() ylabels[k].size = ylabels[k].texture_size y1 = max(y1, ylabels[k].texture_size[0]) ylabels[k].pos = (int(x_next), int(y_start + (ypoints[k] - ymin) * ratio)) if len(ylabels) > 1 and ylabels[0].top > ylabels[1].y: y_overlap = True else: x_next += y1 + padding if len(xlabels) and xlabel_grid: funcexp = exp10 if self.xlog else identity funclog = log10 if self.xlog else identity # find the distance from the end that'll fit the last tick label xlabels[0].text = precision % funcexp(xpoints[-1]) xlabels[0].texture_update() xextent = x + width - xlabels[0].texture_size[0] / 2. - padding # find the distance from the start that'll fit the first tick label if not x_next: xlabels[0].text = precision % funcexp(xpoints[0]) xlabels[0].texture_update() x_next = padding + xlabels[0].texture_size[0] / 2. xmin = funclog(xmin) ratio = (xextent - x_next) / float(funclog(self.xmax) - xmin) right = -1 for k in range(len(xlabels)): xlabels[k].text = precision % funcexp(xpoints[k]) # update the size so we can center the labels on ticks xlabels[k].texture_update() xlabels[k].size = xlabels[k].texture_size half_ts = xlabels[k].texture_size[0] / 2. xlabels[k].pos = (int(x_next + (xpoints[k] - xmin) * ratio - half_ts), int(y_next)) if xlabels[k].x < right: x_overlap = True break right = xlabels[k].right if not x_overlap: y_next += padding + xlabels[0].texture_size[1] # now re-center the x and y axis labels if xlabel: xlabel.x = int(x_next + (xextent - x_next) / 2. - xlabel.width / 2.) if ylabel: ylabel.y = int(y_next + (yextent - y_next) / 2. - ylabel.height / 2.) ylabel.angle = 90 if x_overlap: for k in range(len(xlabels)): xlabels[k].text = '' if y_overlap: for k in range(len(ylabels)): ylabels[k].text = '' return x_next - x, y_next - y, xextent - x, yextent - y def _update_ticks(self, size): # re-compute the positions of the bounding rectangle mesh = self._mesh_rect vert = mesh.vertices if self.draw_border: s0, s1, s2, s3 = size vert[0] = s0 vert[1] = s1 vert[4] = s2 vert[5] = s1 vert[8] = s2 vert[9] = s3 vert[12] = s0 vert[13] = s3 vert[16] = s0 vert[17] = s1 else: vert[0:18] = [0 for k in range(18)] mesh.vertices = vert # re-compute the positions of the x/y axis ticks mesh = self._mesh_ticks vert = mesh.vertices start = 0 xpoints = self._ticks_majorx ypoints = self._ticks_majory xpoints2 = self._ticks_minorx ypoints2 = self._ticks_minory ylog = self.ylog xlog = self.xlog xmin = self.xmin xmax = self.xmax if xlog: xmin = log10(xmin) xmax = log10(xmax) ymin = self.ymin ymax = self.ymax if ylog: ymin = log10(ymin) ymax = log10(ymax) if len(xpoints): top = size[3] if self.x_grid else metrics.dp(12) + size[1] ratio = (size[2] - size[0]) / float(xmax - xmin) for k in range(start, len(xpoints) + start): vert[k * 8] = size[0] + (xpoints[k - start] - xmin) * ratio vert[k * 8 + 1] = size[1] vert[k * 8 + 4] = vert[k * 8] vert[k * 8 + 5] = top start += len(xpoints) if len(xpoints2): top = metrics.dp(8) + size[1] ratio = (size[2] - size[0]) / float(xmax - xmin) for k in range(start, len(xpoints2) + start): vert[k * 8] = size[0] + (xpoints2[k - start] - xmin) * ratio vert[k * 8 + 1] = size[1] vert[k * 8 + 4] = vert[k * 8] vert[k * 8 + 5] = top start += len(xpoints2) if len(ypoints): top = size[2] if self.y_grid else metrics.dp(12) + size[0] ratio = (size[3] - size[1]) / float(ymax - ymin) for k in range(start, len(ypoints) + start): vert[k * 8 + 1] = size[1] + (ypoints[k - start] - ymin) * ratio vert[k * 8 + 5] = vert[k * 8 + 1] vert[k * 8] = size[0] vert[k * 8 + 4] = top start += len(ypoints) if len(ypoints2): top = metrics.dp(8) + size[0] ratio = (size[3] - size[1]) / float(ymax - ymin) for k in range(start, len(ypoints2) + start): vert[k * 8 + 1] = size[1] + (ypoints2[k - start] - ymin) * ratio vert[k * 8 + 5] = vert[k * 8 + 1] vert[k * 8] = size[0] vert[k * 8 + 4] = top mesh.vertices = vert x_axis = ListProperty([None]) y_axis = ListProperty([None]) def get_x_axis(self, axis=0): if axis == 0: return self.xlog, self.xmin, self.xmax info = self.x_axis[axis] return (info["log"], info["min"], info["max"]) def get_y_axis(self, axis=0): if axis == 0: return self.ylog, self.ymin, self.ymax info = self.y_axis[axis] return (info["log"], info["min"], info["max"]) def add_x_axis(self, xmin, xmax, xlog=False): data = {"log": xlog, "min": xmin, "max": xmax} self.x_axis.append(data) return data def add_y_axis(self, ymin, ymax, ylog=False): data = {"log": ylog, "min": ymin, "max": ymax} self.y_axis.append(data) return data def _update_plots(self, size): for plot in self.plots: xlog, xmin, xmax = self.get_x_axis(plot.x_axis) ylog, ymin, ymax = self.get_y_axis(plot.y_axis) plot._update(xlog, xmin, xmax, ylog, ymin, ymax, size) def _update_colors(self, *args): self._mesh_ticks_color.rgba = tuple(self.tick_color) self._background_color.rgba = tuple(self.background_color) self._mesh_rect_color.rgba = tuple(self.border_color) def _redraw_all(self, *args): # add/remove all the required labels xpoints_major, xpoints_minor = self._redraw_x(*args) ypoints_major, ypoints_minor = self._redraw_y(*args) mesh = self._mesh_ticks n_points = (len(xpoints_major) + len(xpoints_minor) + len(ypoints_major) + len(ypoints_minor)) mesh.vertices = [0] * (n_points * 8) mesh.indices = [k for k in range(n_points * 2)] self._redraw_size() def _redraw_x(self, *args): font_size = self.font_size if self.xlabel: xlabel = self._xlabel if not xlabel: xlabel = Label() self.add_widget(xlabel) self._xlabel = xlabel xlabel.font_size = font_size for k, v in self.label_options.items(): setattr(xlabel, k, v) else: xlabel = self._xlabel if xlabel: self.remove_widget(xlabel) self._xlabel = None grids = self._x_grid_label xpoints_major, xpoints_minor = self._get_ticks(self.x_ticks_major, self.x_ticks_minor, self.xlog, self.xmin, self.xmax) self._ticks_majorx = xpoints_major self._ticks_minorx = xpoints_minor if not self.x_grid_label: n_labels = 0 else: n_labels = len(xpoints_major) for k in range(n_labels, len(grids)): self.remove_widget(grids[k]) del grids[n_labels:] grid_len = len(grids) grids.extend([None] * (n_labels - len(grids))) for k in range(grid_len, n_labels): grids[k] = GraphRotatedLabel(font_size=font_size, angle=self.x_ticks_angle, **self.label_options) self.add_widget(grids[k]) return xpoints_major, xpoints_minor def _redraw_y(self, *args): font_size = self.font_size if self.ylabel: ylabel = self._ylabel if not ylabel: ylabel = GraphRotatedLabel() self.add_widget(ylabel) self._ylabel = ylabel ylabel.font_size = font_size for k, v in self.label_options.items(): setattr(ylabel, k, v) else: ylabel = self._ylabel if ylabel: self.remove_widget(ylabel) self._ylabel = None grids = self._y_grid_label ypoints_major, ypoints_minor = self._get_ticks(self.y_ticks_major, self.y_ticks_minor, self.ylog, self.ymin, self.ymax) self._ticks_majory = ypoints_major self._ticks_minory = ypoints_minor if not self.y_grid_label: n_labels = 0 else: n_labels = len(ypoints_major) for k in range(n_labels, len(grids)): self.remove_widget(grids[k]) del grids[n_labels:] grid_len = len(grids) grids.extend([None] * (n_labels - len(grids))) for k in range(grid_len, n_labels): grids[k] = Label(font_size=font_size, **self.label_options) self.add_widget(grids[k]) return ypoints_major, ypoints_minor def _redraw_size(self, *args): # size a 4-tuple describing the bounding box in which we can draw # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left # and top right corner locations, respectively self._clear_buffer() size = self._update_labels() self.view_pos = self._plot_area.pos = (size[0], size[1]) self.view_size = self._plot_area.size = (size[2] - size[0], size[3] - size[1]) if self.size[0] and self.size[1]: self._fbo.size = self.size else: self._fbo.size = 1, 1 # gl errors otherwise self._fbo_rect.texture = self._fbo.texture self._fbo_rect.size = self.size self._fbo_rect.pos = self.pos self._background_rect.size = self.size self._update_ticks(size) self._update_plots(size) def _clear_buffer(self, *largs): fbo = self._fbo fbo.bind() fbo.clear_buffer() fbo.release() def add_plot(self, plot): '''Add a new plot to this graph. :Parameters: `plot`: Plot to add to this graph. >>> graph = Graph() >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) >>> plot.points = [(x / 10., sin(x / 50.)) for x in range(-0, 101)] >>> graph.add_plot(plot) ''' if plot in self.plots: return add = self._plot_area.canvas.add for instr in plot.get_drawings(): add(instr) plot.bind(on_clear_plot=self._clear_buffer) self.plots.append(plot) def remove_plot(self, plot): '''Remove a plot from this graph. :Parameters: `plot`: Plot to remove from this graph. >>> graph = Graph() >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) >>> plot.points = [(x / 10., sin(x / 50.)) for x in range(-0, 101)] >>> graph.add_plot(plot) >>> graph.remove_plot(plot) ''' if plot not in self.plots: return remove = self._plot_area.canvas.remove for instr in plot.get_drawings(): remove(instr) plot.unbind(on_clear_plot=self._clear_buffer) self.plots.remove(plot) self._clear_buffer() def collide_plot(self, x, y): '''Determine if the given coordinates fall inside the plot area. :Parameters: `x, y`: The coordinates to test (in window coords). ''' adj_x, adj_y = x - self._plot_area.pos[0], y - self._plot_area.pos[1] return 0 <= adj_x <= self._plot_area.size[0] \ and 0 <= adj_y <= self._plot_area.size[1] def to_data(self, x, y): '''Convert window coords to data coords. :Parameters: `x, y`: The coordinates to convert (in window coords). ''' adj_x = float(x - self._plot_area.pos[0]) adj_y = float(y - self._plot_area.pos[1]) norm_x = adj_x / self._plot_area.size[0] norm_y = adj_y / self._plot_area.size[1] if self.xlog: xmin, xmax = log10(self.xmin), log10(self.xmax) conv_x = 10.**(norm_x * (xmax - xmin) + xmin) else: conv_x = norm_x * (self.xmax - self.xmin) + self.xmin if self.ylog: ymin, ymax = log10(self.ymin), log10(self.ymax) conv_y = 10.**(norm_y * (ymax - ymin) + ymin) else: conv_y = norm_y * (self.ymax - self.ymin) + self.ymin return [conv_x, conv_y] xmin = NumericProperty(0.) '''The x-axis minimum value. If :data:`xlog` is True, xmin must be larger than zero. :data:`xmin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' xmax = NumericProperty(100.) '''The x-axis maximum value, larger than xmin. :data:`xmax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' xlog = BooleanProperty(False) '''Determines whether the x-axis should be displayed logarithmically (True) or linearly (False). :data:`xlog` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' x_ticks_major = BoundedNumericProperty(0, min=0) '''Distance between major tick marks on the x-axis. Determines the distance between the major tick marks. Major tick marks start from min and re-occur at every ticks_major until :data:`xmax`. If :data:`xmax` doesn't overlap with a integer multiple of ticks_major, no tick will occur at :data:`xmax`. Zero indicates no tick marks. If :data:`xlog` is true, then this indicates the distance between ticks in multiples of current decade. E.g. if :data:`xmin` is 0.1 and ticks_major is 0.1, it means there will be a tick at every 10th of the decade, i.e. 0.1 ... 0.9, 1, 2... If it is 0.3, the ticks will occur at 0.1, 0.3, 0.6, 0.9, 2, 5, 8, 10. You'll notice that it went from 8 to 10 instead of to 20, that's so that we can say 0.5 and have ticks at every half decade, e.g. 0.1, 0.5, 1, 5, 10, 50... Similarly, if ticks_major is 1.5, there will be ticks at 0.1, 5, 100, 5,000... Also notice, that there's always a major tick at the start. Finally, if e.g. :data:`xmin` is 0.6 and this 0.5 there will be ticks at 0.6, 1, 5... :data:`x_ticks_major` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' x_ticks_minor = BoundedNumericProperty(0, min=0) '''The number of sub-intervals that divide x_ticks_major. Determines the number of sub-intervals into which ticks_major is divided, if non-zero. The actual number of minor ticks between the major ticks is ticks_minor - 1. Only used if ticks_major is non-zero. If there's no major tick at xmax then the number of minor ticks after the last major tick will be however many ticks fit until xmax. If self.xlog is true, then this indicates the number of intervals the distance between major ticks is divided. The result is the number of multiples of decades between ticks. I.e. if ticks_minor is 10, then if ticks_major is 1, there will be ticks at 0.1, 0.2...0.9, 1, 2, 3... If ticks_major is 0.3, ticks will occur at 0.1, 0.12, 0.15, 0.18... Finally, as is common, if ticks major is 1, and ticks minor is 5, there will be ticks at 0.1, 0.2, 0.4... 0.8, 1, 2... :data:`x_ticks_minor` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' x_grid = BooleanProperty(False) '''Determines whether the x-axis has tick marks or a full grid. If :data:`x_ticks_major` is non-zero, then if x_grid is False tick marks will be displayed at every major tick. If x_grid is True, instead of ticks, a vertical line will be displayed at every major tick. :data:`x_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' x_grid_label = BooleanProperty(False) '''Whether labels should be displayed beneath each major tick. If true, each major tick will have a label containing the axis value. :data:`x_grid_label` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' xlabel = StringProperty('') '''The label for the x-axis. If not empty it is displayed in the center of the axis. :data:`xlabel` is a :class:`~kivy.properties.StringProperty`, defaults to ''. ''' ymin = NumericProperty(0.) '''The y-axis minimum value. If :data:`ylog` is True, ymin must be larger than zero. :data:`ymin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' ymax = NumericProperty(100.) '''The y-axis maximum value, larger than ymin. :data:`ymax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' ylog = BooleanProperty(False) '''Determines whether the y-axis should be displayed logarithmically (True) or linearly (False). :data:`ylog` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' y_ticks_major = BoundedNumericProperty(0, min=0) '''Distance between major tick marks. See :data:`x_ticks_major`. :data:`y_ticks_major` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' y_ticks_minor = BoundedNumericProperty(0, min=0) '''The number of sub-intervals that divide ticks_major. See :data:`x_ticks_minor`. :data:`y_ticks_minor` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' y_grid = BooleanProperty(False) '''Determines whether the y-axis has tick marks or a full grid. See :data:`x_grid`. :data:`y_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' y_grid_label = BooleanProperty(False) '''Whether labels should be displayed beneath each major tick. If true, each major tick will have a label containing the axis value. :data:`y_grid_label` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' ylabel = StringProperty('') '''The label for the y-axis. If not empty it is displayed in the center of the axis. :data:`ylabel` is a :class:`~kivy.properties.StringProperty`, defaults to ''. ''' padding = NumericProperty('5dp') '''Padding distances between the labels, axes titles and graph, as well between the widget and the objects near the boundaries. :data:`padding` is a :class:`~kivy.properties.NumericProperty`, defaults to 5dp. ''' font_size = NumericProperty('15sp') '''Font size of the labels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, defaults to 15sp. ''' x_ticks_angle = NumericProperty(0) '''Rotate angle of the x-axis tick marks. :data:`x_ticks_angle` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' precision = StringProperty('%g') '''Determines the numerical precision of the tick mark labels. This value governs how the numbers are converted into string representation. Accepted values are those listed in Python's manual in the "String Formatting Operations" section. :data:`precision` is a :class:`~kivy.properties.StringProperty`, defaults to '%g'. ''' draw_border = BooleanProperty(True) '''Whether a border is drawn around the canvas of the graph where the plots are displayed. :data:`draw_border` is a :class:`~kivy.properties.BooleanProperty`, defaults to True. ''' plots = ListProperty([]) '''Holds a list of all the plots in the graph. To add and remove plots from the graph use :data:`add_plot` and :data:`add_plot`. Do not add directly edit this list. :data:`plots` is a :class:`~kivy.properties.ListProperty`, defaults to []. ''' view_size = ObjectProperty((0, 0)) '''The size of the graph viewing area - the area where the plots are displayed, excluding labels etc. ''' view_pos = ObjectProperty((0, 0)) '''The pos of the graph viewing area - the area where the plots are
class Accordion(Widget): '''Accordion class. See module documentation for more information. ''' orientation = OptionProperty('horizontal', options=( 'horizontal', 'vertical')) '''Orientation of the layout. :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and defaults to 'horizontal'. Can take a value of 'vertical' or 'horizontal'. ''' anim_duration = NumericProperty(1) '''Duration of the animation in seconds when a new accordion item is selected. :attr:`anim_duration` is a :class:`~kivy.properties.NumericProperty` and defaults to .25 (250ms). ''' anim_func = ObjectProperty('out_expo') '''Easing function to use for the animation. Check :class:`kivy.animation.AnimationTransition` for more information about available animation functions. :attr:`anim_func` is an :class:`~kivy.properties.ObjectProperty` and defaults to 'out_expo'. You can set a string or a function to use as an easing function. ''' min_space = NumericProperty('44dp') '''Minimum space to use for the title of each item. This value is automatically set for each child every time the layout event occurs. :attr:`min_space` is a :class:`~kivy.properties.NumericProperty` and defaults to 44 (px). ''' def __init__(self, **kwargs): super(Accordion, self).__init__(**kwargs) update = self._trigger_layout = \ Clock.create_trigger(self._do_layout, -1) fbind = self.fbind fbind('orientation', update) fbind('children', update) fbind('size', update) fbind('pos', update) fbind('min_space', update) def add_widget(self, widget, *largs): if not isinstance(widget, AccordionItem): raise AccordionException('Accordion accept only AccordionItem') widget.accordion = self ret = super(Accordion, self).add_widget(widget, *largs) return ret def select(self, instance): if instance not in self.children: raise AccordionException( 'Accordion: instance not found in children') for widget in self.children: widget.collapse = widget is not instance self._trigger_layout() def _do_layout(self, dt): children = self.children if children: all_collapsed = all(x.collapse for x in children) else: all_collapsed = False if all_collapsed: children[0].collapse = False orientation = self.orientation min_space = self.min_space min_space_total = len(children) * self.min_space w, h = self.size x, y = self.pos if orientation == 'horizontal': display_space = self.width - min_space_total else: display_space = self.height - min_space_total if display_space <= 0: Logger.warning('Accordion: not enough space ' 'for displaying all children') Logger.warning('Accordion: need %dpx, got %dpx' % ( min_space_total, min_space_total + display_space)) Logger.warning('Accordion: layout aborted.') return if orientation == 'horizontal': children = reversed(children) for child in children: child_space = min_space child_space += display_space * (1 - child.collapse_alpha) child._min_space = min_space child.x = x child.y = y child.orientation = self.orientation if orientation == 'horizontal': child.content_size = display_space, h child.width = child_space child.height = h x += child_space else: child.content_size = w, display_space child.width = w child.height = child_space y += child_space
class RootWidget(Screen): mapview = ObjectProperty() alpha = ListProperty([1]) wx1 = NumericProperty(randint(10, Window.width)) wy1 = NumericProperty(randint(10, Window.height)) wx2 = NumericProperty(randint(10, Window.width)) wy2 = NumericProperty(randint(10, Window.height)) wx3 = NumericProperty(randint(10, Window.width)) wy3 = NumericProperty(randint(10, Window.height)) wx4 = NumericProperty(randint(10, Window.width)) wy4 = NumericProperty(randint(10, Window.height)) wx5 = NumericProperty(randint(10, Window.width)) wy5 = NumericProperty(randint(10, Window.height)) wx6 = NumericProperty(randint(10, Window.width)) wy6 = NumericProperty(randint(10, Window.height)) wx7 = NumericProperty(randint(10, Window.width)) wy7 = NumericProperty(randint(10, Window.height)) wx8 = NumericProperty(randint(10, Window.width)) wy8 = NumericProperty(randint(10, Window.height)) wx9 = NumericProperty(randint(10, Window.width)) wy9 = NumericProperty(randint(10, Window.height)) wx10 = NumericProperty(randint(10, Window.width)) wy10 = NumericProperty(randint(10, Window.height)) wx11 = NumericProperty(randint(10, Window.width)) wy11 = NumericProperty(randint(10, Window.height)) wx12 = NumericProperty(randint(10, Window.width)) wy12 = NumericProperty(randint(10, Window.height)) wx13 = NumericProperty(randint(10, Window.width)) wy13 = NumericProperty(randint(10, Window.height)) wx14 = NumericProperty(randint(10, Window.width)) wy14 = NumericProperty(randint(10, Window.height)) r1 = NumericProperty(randint(10, Window.height//6)) r2 = NumericProperty(randint(10, Window.height//6)) r3 = NumericProperty(randint(10, Window.height//6)) r4 = NumericProperty(randint(10, Window.height//6)) r5 = NumericProperty(randint(10, Window.height//6)) r6 = NumericProperty(randint(10, Window.height//6)) r7 = NumericProperty(randint(10, Window.height//6)) r8 = NumericProperty(randint(10, Window.height//6)) r9 = NumericProperty(randint(10, Window.height//6)) r10 = NumericProperty(randint(10, Window.height//6)) r11 = NumericProperty(randint(10, Window.height//6)) r12 = NumericProperty(randint(10, Window.height//6)) r13 = NumericProperty(randint(10, Window.height//6)) r14 = NumericProperty(randint(10, Window.height//6))
def test_reference(self): from kivy.properties import NumericProperty, ReferenceListProperty x = NumericProperty(0) x.link(wid, 'x') x.link_deps(wid, 'x') y = NumericProperty(0) y.link(wid, 'y') y.link_deps(wid, 'y') pos = ReferenceListProperty(x, y) pos.link(wid, 'pos') pos.link_deps(wid, 'pos') self.assertEqual(x.get(wid), 0) self.assertEqual(y.get(wid), 0) self.assertEqual(pos.get(wid), [0, 0]) x.set(wid, 50) self.assertEqual(pos.get(wid), [50, 0]) y.set(wid, 50) self.assertEqual(pos.get(wid), [50, 50]) pos.set(wid, [0, 0]) self.assertEqual(pos.get(wid), [0, 0]) self.assertEqual(x.get(wid), 0) self.assertEqual(y.get(wid), 0) # test observer global observe_called observe_called = 0 def observe(obj, value): global observe_called observe_called = 1 pos.bind(wid, observe) self.assertEqual(observe_called, 0) x.set(wid, 99) self.assertEqual(observe_called, 1)
def test_numeric_string_with_units_check(self): from kivy.properties import NumericProperty a = NumericProperty() a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) a.set(wid, '55dp') from kivy.core.window import Window density = Window._density if hasattr(Window, '_density') else 1 self.assertEqual(a.get(wid), 55 * density) self.assertEqual(a.get_format(wid), 'dp') a.set(wid, u'55dp') self.assertEqual(a.get(wid), 55 * density) self.assertEqual(a.get_format(wid), 'dp') a.set(wid, '99in') self.assertEqual(a.get(wid), 9504.0 * density) self.assertEqual(a.get_format(wid), 'in') a.set(wid, u'99in') self.assertEqual(a.get(wid), 9504.0 * density) self.assertEqual(a.get_format(wid), 'in')
class Igra(Widget): lopta = ObjectProperty(None) igrac1 = ObjectProperty(None) igrac2 = ObjectProperty(None) slijed = BooleanProperty( None) #provjerava ako si kliknuo igraca koji je na redu red = NumericProperty( 1) #broji red odigranih koraka kako bi se znalo tko je na redu publika = SoundLoader.load("source/sound1.mp3").play() goal = SoundLoader.load("source/sound2.mp3") #kad se klikne provjerava koji je igrac kliknut def on_touch_down(self, touch): if self.igrac1.collide_point(*touch.pos): self.slijed = True elif self.igrac2.collide_point(*touch.pos): self.slijed = False #kad se otpusti klik, provjerava zadnja pozicija klika te se računa brzina koja se nadodaje igraču def on_touch_up(self, touch): if self.slijed and self.red % 2 == 1: self.igrac1.velocity_x = int( (self.igrac1.center_x - touch.x) * 3 / 4) self.igrac1.velocity_y = int( (self.igrac1.center_y - touch.y) * 3 / 4) self.slijed = BooleanProperty(None) self.red += 1 elif self.red % 2 == 0 and not self.slijed: self.igrac2.velocity_x = int( (self.igrac2.center_x - touch.x) * 3 / 4) self.igrac2.velocity_y = int( (self.igrac2.center_y - touch.y) * 3 / 4) self.slijed = BooleanProperty(None) self.red += 1 def restart(self): self.goal.play() self.lopta.center = (300, 454) self.lopta.velocity = (0, 0) self.igrac1.center = (300, 200) self.igrac1.velocity = (0, 0) self.igrac2.center = (300, 708) self.igrac2.velocity = (0, 0) def update(self, dt): #pokretanje funkcije kretanja igraca, 60x u sekundi provodi micanje igraca #provjerava i provodi odbijanje između igrača i igrača s loptom if self.igrac1.velocity_y != 0 or self.igrac2.velocity_x != 0: self.igrac1.move() self.igrac1.player_collide(self.igrac2) self.igrac1.player_collide(self.lopta) if self.igrac2.velocity_y != 0 or self.igrac2.velocity_y != 0: self.igrac2.move() self.igrac2.player_collide(self.igrac1) self.igrac2.player_collide(self.lopta) if self.lopta.velocity_y != 0 or self.lopta.velocity_x != 0: self.lopta.move() self.lopta.ball_collide(self.igrac1) self.lopta.ball_collide(self.igrac2) #Odbijanje od zidova terena if (self.igrac1.x <= 0) or (self.igrac1.right >= 600): self.igrac1.velocity_x *= -1 self.igrac1.move() if (self.igrac1.y <= 0) or (self.igrac1.top >= 908): self.igrac1.velocity_y *= -1 self.igrac1.move() if (self.igrac2.x <= 0) or (self.igrac2.right >= 600): self.igrac2.velocity_x *= -1 self.igrac2.move() if (self.igrac2.y <= 0) or (self.igrac2.top >= 908): self.igrac2.velocity_y *= -1 self.igrac2.move() #Odbijanje lopte od zidova i restartanje igre u slučaju gola if (self.lopta.x <= 0) or (self.lopta.right >= 600): self.lopta.velocity_x *= -1 self.lopta.move() if (self.lopta.y <= 0): self.igrac2.score += 1 self.restart() elif (self.lopta.top >= 908): self.igrac1.score += 1 self.restart()
class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem): # dp(40) = dp(16) + dp(24): _txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
class PhysicsObject(Widget): angle = NumericProperty() scale = NumericProperty(1.0) source = StringProperty() color = ListProperty([1, 1, 1, 1]) constraints = [0, 100, 0, 100] constraint_x = BooleanProperty(True) constraint_y = BooleanProperty(True) _vectors = {} _trajectory = collections.deque() _trajectory_points = 100 _trajectory_resolution = 0.05 show_trajectory = BooleanProperty(False) draggable = BooleanProperty(False) def __init__(self, **kwargs): self.register_event_type('on_drag') super(PhysicsObject, self).__init__(**kwargs) def add_vector(self, name, title, length, angle=0.0, color=(1, 1, 1, 1), mode='object'): self._vectors[name] = VectorWidget(title=title, length=length, angle=angle, color=color, mode=mode) self.add_widget(self._vectors[name]) def update(self, dt): if len(self._trajectory) == 0: self._trajectory.appendleft((self.pos[0], self.pos[1])) angle0 = math.atan2(self._trajectory[0][1] - self.pos[1], self._trajectory[0][0] - self.pos[0]) if len(self._trajectory) > 1: angle1 = math.atan2( self._trajectory[1][1] - self._trajectory[0][1], self._trajectory[1][0] - self._trajectory[0][0]) else: angle1 = 0 if math.fabs(angle0 - angle1) > self._trajectory_resolution: self._trajectory.appendleft((self.pos[0], self.pos[1])) if len(self._trajectory) > self._trajectory_points: self._trajectory.pop() def after_update(self, dt): max_size = max(self.width, self.height) / 2 if self.constraint_x: if self.x < self.constraints[0] + max_size: self.x = self.constraints[0] + max_size if self.x > self.constraints[1] - max_size: self.x = self.constraints[1] - max_size if self.constraint_y: if self.y < self.constraints[2] + max_size: self.y = self.constraints[2] + max_size if self.y > self.constraints[3] - max_size: self.y = self.constraints[3] - max_size def update_vector(self, name, length, angle=None): vector = self._vectors[name] vector.name = name vector.length = length if angle is not None: vector.angle = angle def on_pos(self, *largs): for vector in self._vectors.itervalues(): vector.pos = self.pos def clear_trajectory(self): self._trajectory.clear() def init(self, *largs): self.clear_trajectory() def on_drag(self, touch): pass def collide_point(self, x, y): max_size = max(self.width, self.height) / 2 return self.x - max_size < x < self.x + max_size and self.y - max_size < y < self.y + max_size def on_touch_down(self, touch): if super(PhysicsObject, self).on_touch_down(touch): return True if touch.is_mouse_scrolling: return False if not self.collide_point(touch.x, touch.y): return False if self in touch.ud: return False touch.grab(self) touch.ud[self] = True self.dispatch('on_drag', touch) return True def on_touch_move(self, touch): if touch.grab_current is self: self.dispatch('on_drag', touch) return True if super(PhysicsObject, self).on_touch_move(touch): return True return self in touch.ud def on_touch_up(self, touch): if touch.grab_current is not self: return super(PhysicsObject, self).on_touch_up(touch) self.dispatch('on_drag', touch) touch.ungrab(self) return True
def test_numericcheck(self): from kivy.properties import NumericProperty a = NumericProperty(0) a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), 0) a.set(wid, 99) self.assertEqual(a.get(wid), 99) try: a.set(wid, '') # string shouldn't be accepted self.fail('number accept string, fail.') except ValueError: pass
class MDDatePicker(FloatLayout, ThemableBehavior, ElevationBehavior, ModalView): _sel_day_widget = ObjectProperty() cal_list = None cal_layout = ObjectProperty() sel_year = NumericProperty() sel_month = NumericProperty() sel_day = NumericProperty() day = NumericProperty() month = NumericProperty() year = NumericProperty() today = date.today() callback = ObjectProperty() class SetDateError(Exception): pass def __init__(self, callback, year=None, month=None, day=None, firstweekday=0, **kwargs): self.callback = callback self.cal = calendar.Calendar(firstweekday) self.sel_year = year if year else self.today.year self.sel_month = month if month else self.today.month self.sel_day = day if day else self.today.day self.month = self.sel_month self.year = self.sel_year self.day = self.sel_day super(MDDatePicker, self).__init__(**kwargs) self.selector = DaySelector(parent=self) self.generate_cal_widgets() self.update_cal_matrix(self.sel_year, self.sel_month) self.set_month_day(self.sel_day) self.selector.update() def ok_click(self): self.callback(date(self.sel_year, self.sel_month, self.sel_day)) self.dismiss() def fmt_lbl_date(self, year, month, day, orientation): d = datetime.date(int(year), int(month), int(day)) separator = '\n' if orientation == 'landscape' else ' ' return d.strftime('%a,').capitalize() + separator + d.strftime( '%b').capitalize() + ' ' + str(day).lstrip('0') def set_date(self, year, month, day): try: date(year, month, day) except Exception as e: print(e) if str(e) == "day is out of range for month": raise self.SetDateError( " Day %s day is out of range for month %s" % (day, month)) elif str(e) == "month must be in 1..12": raise self.SetDateError( "Month must be between 1 and 12, got %s" % month) elif str(e) == "year is out of range": raise self.SetDateError( "Year must be between %s and %s, got %s" % (datetime.MINYEAR, datetime.MAXYEAR, year)) else: self.sel_year = year self.sel_month = month self.sel_day = day self.month = self.sel_month self.year = self.sel_year self.day = self.sel_day self.update_cal_matrix(self.sel_year, self.sel_month) self.set_month_day(self.sel_day) self.selector.update() def set_selected_widget(self, widget): if self._sel_day_widget: self._sel_day_widget.is_selected = False widget.is_selected = True self.sel_month = int(self.month) self.sel_year = int(self.year) self.sel_day = int(widget.text) self._sel_day_widget = widget self.selector.set_widget(widget) def set_month_day(self, day): for idx in range(len(self.cal_list)): if str(day) == str(self.cal_list[idx].text): self._sel_day_widget = self.cal_list[idx] self.sel_day = int(self.cal_list[idx].text) if self._sel_day_widget: self._sel_day_widget.is_selected = False self._sel_day_widget = self.cal_list[idx] self.cal_list[idx].is_selected = True self.selector.set_widget(self.cal_list[idx]) def update_cal_matrix(self, year, month): try: dates = [x for x in self.cal.itermonthdates(year, month)] except ValueError as e: if str(e) == "year is out of range": pass else: self.year = year self.month = month for idx in range(len(self.cal_list)): if idx >= len(dates) or dates[idx].month != month: self.cal_list[idx].disabled = True self.cal_list[idx].text = '' else: self.cal_list[idx].disabled = False self.cal_list[idx].text = str(dates[idx].day) self.cal_list[idx].is_today = dates[idx] == self.today self.selector.update() def generate_cal_widgets(self): cal_list = [] for i in calendar.day_abbr: self.cal_layout.add_widget(WeekdayLabel(text=i[0].upper())) for i in range(6 * 7): # 6 weeks, 7 days a week db = DayButton(owner=self) cal_list.append(db) self.cal_layout.add_widget(db) self.cal_list = cal_list def change_month(self, operation): op = 1 if operation is 'next' else -1 sl, sy = self.month, self.year m = 12 if sl + op == 0 else 1 if sl + op == 13 else sl + op y = sy - 1 if sl + op == 0 else sy + 1 if sl + op == 13 else sy self.update_cal_matrix(y, m)
class YourAppNameApp(App): count = NumericProperty(0)
class AnalogeGauge(Widget): ''' Gauge class ''' unit = NumericProperty(1.8) size_text = NumericProperty(10) value = BoundedNumericProperty(0, min_value=0, max_value=100, errorvalue=0) start_angle = BoundedNumericProperty(90, min_value=-360, max_value=360, errorvalue=0) angle_width = BoundedNumericProperty(180, min_value=0, max_value=360, errorvalue=0) min_value = NumericProperty(0) max_value = NumericProperty(100) rotate_clock = BooleanProperty(True) file_background = StringProperty("cadran.png") file_gauge = StringProperty("") file_needle = StringProperty("") half_widget_view = BooleanProperty(False) padding = BoundedNumericProperty(10, min_value=0, max_value=360, errorvalue=0) mark_count = BoundedNumericProperty(10, min_value=0, max_value=360, errorvalue=0) mark_sub_count = BoundedNumericProperty(10, min_value=0, max_value=100, errorvalue=0) show_middle_marks = BooleanProperty(True) show_sub_marks = BooleanProperty(True) mark_size = BoundedNumericProperty(20, min_value=0, max_value=300, errorvalue=0) mark_mid_size = BoundedNumericProperty(15, min_value=0, max_value=300, errorvalue=0) mark_sub_size = BoundedNumericProperty(10, min_value=0, max_value=300, errorvalue=0) mark_color = ColorProperty('#ffffffff') mark_sub_color = ColorProperty('#ffffffff') mark_mid_color = ColorProperty('#ffffffff') needle_color = ColorProperty('#ff0000ff') glab_color = ColorProperty('#ff0000ff') def __init__(self, **kwargs): super(AnalogeGauge, self).__init__(**kwargs) self._gauge_widget = Widget() self._gauge = self._gauge_widget self._needle_widget_safe = Widget() self._needle_widget = self._needle_widget_safe self._form_processor_constants() self._needle = Scatter(size=self.size, do_rotation=False, do_scale=False) self._background_widget = Widget() self._background = Scatter(size=self.size, do_rotation=False, do_scale=False) self._glab = Label(font_size=self.size_text, markup=True, font_name='digital') self._needle.add_widget(self._needle_widget) self.add_widget(self._background) self.add_widget(self._gauge) self.add_widget(self._glab) self.add_widget(self._needle) self.bind(pos=self._update) self.bind(size=self._update) self.bind(value=self._turn) self.bind(file_gauge=self._reform_widget_graphics) self.bind(file_needle=self._reform_widget_graphics) self.bind(file_background=self._reform_widget_graphics) self.bind(min_value=self._form_processor_constants) self.bind(rotate_clock=self._form_processor_constants) self.bind(max_value=self._form_processor_constants) self.bind(start_angle=self._form_processor_constants) self.bind(angle_width=self._form_processor_constants) self.bind(mark_color=self._create_gaudge) self.bind(mark_sub_color=self._create_gaudge) self.bind(mark_mid_color=self._create_gaudge) self.bind(show_middle_marks=self._create_gaudge) self.bind(show_middle_marks=self._create_gaudge) self.bind(mark_count=self._create_gaudge) self.bind(mark_sub_count=self._create_gaudge) self.bind(needle_color=self._create_needle) self.bind(padding=self._update) self._update() self._reform_widget_graphics() self._turn() def _form_processor_constants(self, *args): self.property('value').set_min(self, self.min_value) self.property('value').set_max(self, self.max_value) self.unit = self.angle_width / abs(self.max_value - self.min_value) * ( -1 if self.rotate_clock else 1) #print(self.unit, self.angle_width) def _reform_widget_graphics(self, *args): #print(self.size) if self.file_gauge: self.remove_widget(self._gauge) self._gauge = get_module_resource_path(self.file_gauge, size=self.size, resource_package=__name__) self.add_widget(self._gauge) else: self.remove_widget(self._gauge) self._gauge = self._gauge_widget self.add_widget(self._gauge) if self.file_background: self._background.remove_widget(self._background_widget) self._background_widget = get_module_resource_path( self.file_background, size=self.size, resource_package=__name__) self._background.add_widget(self._background_widget) if self.file_needle: self._needle.remove_widget(self._needle_widget) self._needle_widget = get_module_resource_path( self.file_needle, size=self.size, resource_package=__name__) self._needle.add_widget(self._needle_widget) else: self._needle.remove_widget(self._needle_widget) self._needle_widget = self._needle_widget_safe self._needle.add_widget(self._needle_widget) def _turn(self, *args): ''' Turn needle, 1 degree = 1 unit, 0 degree point start on 50 value. ''' self._needle.center_x = self._gauge.center_x self._needle.center_y = self._gauge.center_y self._needle.rotation = self.start_angle + (self.value - self.min_value) * self.unit #print(self.start_angle, self.unit,self.value,self.min_value,self._needle.rotation) self._glab.text = "[b]{0:.0f}[/b]".format(self.value) def _create_needle(self, *args): if self._needle_widget == self._needle_widget_safe: self._needle_widget_safe.canvas.clear() with self._needle_widget_safe.canvas: Color(*self.needle_color) Line(points=(*self._needle_widget_safe.center, self._needle_widget_safe.center_x, self._needle_widget_safe.center_y + self.circle_radius)) Line(points=(*self._needle_widget_safe.center, self._needle_widget_safe.center_x, self._needle_widget_safe.center_y + self.circle_radius - 20), width=1.5) Ellipse(pos=(self._needle_widget_safe.center_x - 5, self._needle_widget_safe.center_y - 5), size=(10, 10)) def _create_gaudge(self, *args): if self._gauge == self._gauge_widget: self._gauge_widget.canvas.clear() if self.mark_count > 0: delta_mark = self.angle_width / self.mark_count mark_width = 10 mark_end = min(self.width, self.height) / 2 with self._gauge_widget.canvas: if self.show_sub_marks: Color(*self.mark_sub_color) sub_delta_mark = delta_mark / self.mark_sub_count count = self.mark_count * self.mark_sub_count + 1 sub_start_size = self.circle_radius - self.mark_sub_size for i in range(count): Line(points=get_mark_vector( *self.circle_pos, sub_start_size, self.mark_sub_size, self.start_angle - sub_delta_mark * i)) if self.show_middle_marks: Color(*self.mark_mid_color) sub_delta_mark = delta_mark / 2 count = self.mark_count * 2 + 1 sub_start_size = self.circle_radius - self.mark_mid_size for i in range(count): Line(points=get_mark_vector( *self.circle_pos, sub_start_size, self.mark_mid_size, self.start_angle - sub_delta_mark * i)) Color(*self.mark_color) start_size = self.circle_radius - self.mark_size for i in range(self.mark_count + 1): Line(points=get_mark_vector( *self.circle_pos, start_size, self.mark_size, self.start_angle - delta_mark * i)) def _update(self, *args): ''' Update gauge and needle positions after sizing or positioning. ''' if self.half_widget_view: self.circle_radius = min(*self.size) - self.padding * 2 self.circle_size = (self.circle_radius, self.circle_radius) self.circle_pos = (self.center_x, self.center_y - self.circle_radius / 2) self._bg_pos = (self.x, self.y - self.circle_radius / 2) else: self.circle_radius = min(*self.size) / 2 - self.padding self.circle_size = (self.circle_radius, self.circle_radius) self.circle_pos = self.center self._bg_pos = (self.x, self.y) self._needle.size = self.size self._gauge.size = self.size self._gauge.pos = self.pos self._gauge.center = self.circle_pos self._needle.pos = (self.x, self.y) self._needle.center = self.circle_pos self._background.pos = self._bg_pos self._needle_widget.size = self.size self._background_widget.size = self.size self._glab.center_x = self._gauge.center_x self._glab.center_y = self.center_y + (self.height / 4) self._create_gaudge() self._create_needle() def set_animate(self, value, easing='in_out_quad', speed=1): from kivy.animation import Animation Animation(value=value, duration=speed, t=easing).start(self)