class MainMenu(GridLayout): padding = VariableListProperty(100) spacing = VariableListProperty(10, length=2) learn_button = Button(text=_learn, size_hint=(.3, .3)) test_button = Button(text=_test, size_hint=(.3, .3)) exit_button = Button(text=_exit, size_hint=(.3, .3)) def __init__(self, **kwargs): super(MainMenu, self).__init__(**kwargs) self.show_menu() def show_menu(self): self.add_widget(self.learn_button) self.add_widget(self.test_button) self.add_widget(self.exit_button) self.learn_button.bind(on_press=self.begin_learning) self.test_button.bind(on_press=self.begin_test) self.exit_button.bind(on_press=self.exit_app) def begin_learning(self, learn_button): kodownik_window = self.parent kodownik_window.show_learning_screen() pass def begin_test(self, test_button): kodownik_window = self.parent kodownik_window.show_testing_screen() def exit_app(self, exit_button): App.get_running_app().stop()
class MDTabsLabel(ToggleButtonBehavior, Label): """MDTabsLabel it represent the label of each tab.""" text_color_normal = VariableListProperty([1, 1, 1, 0.6]) """Text color of the label when it is not selected.""" text_color_active = VariableListProperty([1]) """Text color of the label when it is selected.""" tab = ObjectProperty() tab_bar = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) self.min_space = 0 def on_release(self): # if the label is selected load the relative tab from carousel if self.state == "down": self.tab_bar.parent.carousel.load_slide(self.tab) def on_texture(self, widget, texture): # just save the minimum width of the label based of the content if texture: self.width = texture.width self.min_space = self.width def _trigger_update_tab_indicator(self): # update the position and size of the indicator # when the label changes size or position if self.state == "down": self.tab_bar.update_indicator(self.x, self.width)
class CodeScreen(GridLayout): padding = VariableListProperty(100) spacing = VariableListProperty(10, length=2) code_manager = CodeManager() product_name = ProductName() product_code = ProductCode() screen_keyboard = ScreenKeyboard(cols=1, rows=1) submit_buttons = SubmitButtons(cols=2, rows=1, size_hint=(1, .3)) app_buttons = AppButtons(cols=2, rows=1, size_hint=(1, .3)) def __init__(self, **kwargs): super(CodeScreen, self).__init__(**kwargs) self.add_widget(self.product_code) self.add_widget(self.screen_keyboard) self.add_widget(self.submit_buttons) self.add_widget(self.product_name) self.add_widget(self.app_buttons) self.learning = LearningWorkflow( cm=self.code_manager, product_code=self.product_code, product_name=self.product_name, screen_keyboard=self.screen_keyboard, submit_buttons=self.submit_buttons ) self.begin_learning() def begin_learning(self): kodlog.info("I started showing codes") event_dispatcher.do_pick_a_product()
class Box(Widget): padding = VariableListProperty(6) border = VariableListProperty(4) font_size = StringProperty('15sp') font_name = StringProperty(DEFAULT_FONT) background = StringProperty() background_color = VariableListProperty([1, 1, 1, 1]) foreground_color = VariableListProperty([0, 0, 0, 1])
class PlaybackSlider(RelativeLayout): # Playback state data duration = NumericProperty(None, allownone=True) position = NumericProperty(None, allownone=True) seekable = BooleanProperty(True) # Resolution of the data in seconds. Defaults to 1ms resolution = NumericProperty(0.001) # Text to display when no time is availabe default_text = StringProperty('--:--') # Seconds on -Xs and +Xs controls shortcut_secs = VariableListProperty(0, length=2) available = AliasProperty( lambda self: self.position is not None \ and self.duration is not None, None, bind=['position', 'duration'] ) def __init__(self, **kwargs): super(PlaybackSlider, self).__init__(**kwargs) self.register_event_type('on_seek') def on_seek(self, value): pass
class MDNavigationDrawerDivider(MDBoxLayout): """ Implements a divider for a menu for :class:`~MDNavigationDrawer` class. .. versionadded:: 1.0.0 .. code-block:: kv MDNavigationDrawer: MDNavigationDrawerMenu: MDNavigationDrawerLabel: text: "Mail" MDNavigationDrawerDivider: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-divider.png :align: center """ padding = VariableListProperty(["20dp", "12dp", 0, "12dp"]) """ Padding between layout box and 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]. :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and defaults to `['20dp', '12dp', 0, '12dp']`. """ color = ColorProperty(None) """
class MDNavigationDrawerLabel(MDBoxLayout): """ Implements a label for a menu for :class:`~MDNavigationDrawer` class. .. versionadded:: 1.0.0 .. code-block:: kv MDNavigationDrawer: MDNavigationDrawerMenu: MDNavigationDrawerLabel: text: "Mail" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-label.png :align: center """ text = StringProperty() """ Text label. :attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults to `''`. """ padding = VariableListProperty(["20dp", 0, 0, "8dp"]) """
class InfoPage(GridLayout): """This class contains the widgets for the second screen. As well as some more information regarding dates and users""" padding = VariableListProperty([50, 50, 50, 50]) def __init__(self, **kwargs): super().__init__(**kwargs) today = str(datetime.date.today()) if os.path.isfile("prev_details.txt"): # Checks to see if the file exists. with open("prev_details.txt", "r") as f: d = f.read().split(",") prev_date = d[0] if prev_date != today: # Checks if a day has passed. days = int(d[1]) + 1 else: days = int(d[1]) try: users = app('ca.gc.hcsc.canada.covid19').get("installs") # Finds how many people have the app. except ConnectionError: # For now just looks at the Canada COVID19 app. users = d[2] else: days = 0 users = "100,000+" with open("prev_details.txt", "w") as f: # Writes new information to file. f.write(f"{today},{days},{users}") self.cols = 1 # This shows text on the screen. self.add_widget(Label(text=f"You are on day {days} of quarantine with us. Keep it up!", font_name="DejaVuSans")) self.add_widget(Image(source='logo.png')) self.add_widget(Label(text=f"You aren't alone! There are {users} people with you!", font_name="DejaVuSans"))
class ConnectPage(GridLayout): """This class contains all the widgets for the first screen. """ padding = VariableListProperty([50, 50, 50, 50]) def __init__(self, **kwargs): super().__init__(**kwargs) self.cols = 3 self.add_widget(Label()) self.add_widget(Label(text="Do you pledge to do the following:", halign="left", font_name="DejaVuSans")) self.add_widget(Label()) self.add_widget(Image(source="wash.png")) self.add_widget(Label(text="Wash your hands often", halign="left", font_name="DejaVuSans")) self.wash = CheckBox() self.add_widget(self.wash) self.add_widget(Image(source="contact.png")) self.add_widget(Label(text="Avoid close contact", halign="left", font_name="DejaVuSans")) self.contact = CheckBox() self.add_widget(self.contact) self.add_widget(Image(source="mask.png")) self.add_widget(Label(text="Wear a mask properly", halign="left", font_name="DejaVuSans")) self.mask = CheckBox() self.add_widget(self.mask) self.add_widget(Image(source="cover.png")) self.add_widget(Label(text="Cover coughs and sneezes", halign="left", font_name="DejaVuSans")) self.cover = CheckBox() self.add_widget(self.cover) self.add_widget(Image(source="clean.png")) self.add_widget(Label(text="Clean and disinfect", halign="left", font_name="DejaVuSans")) self.clean = CheckBox() self.add_widget(self.clean) self.add_widget(Image(source="monitor.png")) self.add_widget(Label(text="Monitor your health", halign="left", font_name="DejaVuSans")) self.monitor = CheckBox() self.add_widget(self.monitor) self.join = Button(text="Submit", background_color=[0, 0, 1.5, 1.5]) self.join.bind(on_press=self.submit_button) self.add_widget(Label()) self.add_widget(self.join) def submit_button(self, instance): """This function ensures the user selects all the options before moving forward. """ if self.wash.active \ and self.contact.active \ and self.mask.active \ and self.cover.active \ and self.clean.active \ and self.monitor.active: todo_app.screen_manager.current = "Info"
class FlatIconButton(GrabBehavior, LogBehavior, ButtonBehavior, TouchRippleBehavior, ThemeBehavior, AnchorLayout): color = ListProperty([1., 1., 1.]) color_down = ListProperty([.7, .7, .7]) text = StringProperty('') icon = StringProperty('') style = StringProperty(None, allownone=True) font_size = NumericProperty(12) icon_color_tuple = ListProperty(['Grey', '1000']) color_tuple = ListProperty(['Blue', '500']) font_color_tuple = ListProperty(['Grey', '1000']) font_ramp_tuple = ListProperty(None) ripple_color_tuple = ListProperty(['Grey', '1000']) content_padding = VariableListProperty([0., 0., 0., 0.]) content_spacing = VariableListProperty([0., 0.], length=2) def on_color(self, instance, value): self.color_down = [x*.7 for x in value]
class arc_reacter(Widget): circlearc1 = ObjectProperty(None) circlearc2 = ObjectProperty(None) line1 = ObjectProperty(None) color_code = VariableListProperty(None) def update(self, color_code): self.color_code = random.choice([(.811, .854, .921, 1), (.933, .968, .964, 1)])
class ScrollableText(ScrollView): text = StringProperty("") background_color = VariableListProperty([1,1,1,0]) text_color = VariableListProperty([0,0,0,1]) padding = VariableListProperty([sp(8), sp(8), sp(8), sp(8)]) font_size = NumericProperty(sp(18)) def __init__(self, **kwargs): super(ScrollableText, self).__init__(**kwargs) self.bind(text = ScrollableText.set_text, text_color = ScrollableText.set_text_color, padding = ScrollableText.set_padding, font_size = ScrollableText.set_font_size ) def set_text(self, aText): self._text_widget.text = aText def set_text_color(self, aColor): self._text_widget.color = aColor def set_padding(self, aPadding): """Top, Right, Bottom, Left """ if isinstance(aPadding, (float,int,long,float)): self._padding_top.height = aPadding self._padding_right.width = aPadding self._padding_bottom.height = aPadding self._padding_left.width = aPadding if len(aPadding) == 4: self._padding_top.height = aPadding[0] self._padding_right.width = aPadding[1] self._padding_bottom.height = aPadding[2] self._padding_left.width = aPadding[3] elif len(aPadding) == 2: self._padding_top.height = aPadding[0] self._padding_right.width = aPadding[1] self._padding_bottom.height = aPadding[0] self._padding_left.width = aPadding[1] def set_font_size(self, aSize): self._text_widget.font_size = aSize
class DexEntry(FloatLayout, Row): name = StringProperty('') name_lookup = StringProperty('') dex_number = StringProperty('') type1 = StringProperty('') entry = BooleanProperty() lucky = BooleanProperty() fc = BooleanProperty() pic_source = StringProperty('') color_regular = VariableListProperty(1) color_light = VariableListProperty(1) color_dark = VariableListProperty(1) def __init__(self, data_row, data_buffer, import_data, **kwargs): super(DexEntry, self).__init__(data_row=data_row, data_buffer=data_buffer, import_data=import_data, **kwargs) self.data_row = data_row self.name_lookup = str(data_row['Name'].lower()) self.pic_source = PIC_DIR + str(self.dex_number).zfill(3) + '.png' self.color_regular = ColorPicker(self.type1, 'regular') self.color_light = ColorPicker(self.type1, 'light') self.color_dark = ColorPicker(self.type1, 'dark')
class FixedProperties(object): fixed_screen_size = VariableListProperty([1920, 1080], limit=2) # # initial sizing somewhat inspired by Google Material font_title_size_fixed = NumericProperty(80.0) font_subheading_size_fixed = NumericProperty(64.0) font_size_fixed = NumericProperty(56.0) # pos_fixed = ObjectProperty((0, 0)) size_fixed = ObjectProperty((100, 100)) spot_fixed = ObjectProperty((0, 0)) # active_pos_fixed = ObjectProperty((0, 0)) def apply_fixed_properties(self, widget): if not hasattr(widget, "fixed_screen_size"): x = self.fixed_screen_size[X] y = self.fixed_screen_size[Y] widget.apply_property(fixed_screen_size=ObjectProperty([x, y])) # if not hasattr(widget, "font_title_size_fixed"): widget.apply_property(font_title_size_fixed=ObjectProperty((80.0))) if not hasattr(widget, "font_subheading_size_fixed"): widget.apply_property( font_subheading_size_fixed=ObjectProperty((56.0))) if not hasattr(widget, "font_size_fixed"): widget.apply_property(font_size_fixed=ObjectProperty((56.0))) # if not hasattr(widget, "pos_fixed"): widget.apply_property(pos_fixed=ObjectProperty((0, 0))) if not hasattr(widget, "size_fixed"): widget.apply_property(size_fixed=ObjectProperty((100, 100))) if not hasattr(widget, "spot_fixed"): widget.apply_property(spot_fixed=ObjectProperty((0, 0))) # if not hasattr(widget, "active_pos_fixed"): widget.apply_property(active_pos_fixed=ObjectProperty((0, 0))) # we force stretching to True widget.allow_stretch = True
class FitImage(BoxLayout): source = ObjectProperty() """ Filename/source of your image. :attr:`source` is a :class:`~kivy.properties.StringProperty` and defaults to None. """ radius = VariableListProperty([0], length=4) """ Canvas radius. .. code-block:: python # Top left corner slice. MDBoxLayout: md_bg_color: app.theme_cls.primary_color radius: [25, 0, 0, 0] :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` and defaults to `[0, 0, 0, 0]`. """ _container = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) Clock.schedule_once(self._late_init) def _late_init(self, *args): self._container = Container(self.source) self.bind(source=self._container.setter("source")) self.add_widget(self._container) def reload(self): self._container.image.reload()
class IconText(BoxLayout): text = StringProperty("") icon = StringProperty("") icon_size = NumericProperty(sp(80)) font_size = NumericProperty(sp(18)) text_color = VariableListProperty([0, 0, 0, 1]) forced_width = NumericProperty(sp(80)) def __init__(self, **kwargs): super(IconText, self).__init__(**kwargs) self.orientation = 'vertical' self.bind(text=IconText.set_text, icon=IconText.set_icon, icon_size=IconText.set_icon_size, font_size=IconText.set_font_size, text_color=IconText.set_text_color, forced_width=IconText.set_forced_width) def set_text(self, aText): self._text_widget.text = aText def set_icon(self, aSourceImage): self._icon_widget.canvas.before.children[1].source = aSourceImage def set_icon_size(self, aWidthHeight): self._icon_widget.size = (aWidthHeight, aWidthHeight) def set_font_size(self, aFontSize): self._text_widget.font_size = aFontSize def set_text_color(self, aColor): self._text_widget.color = aColor def set_forced_width(self, aWidth): if aWidth is not None: self.size_hint_x = None self.width = aWidth
class AndroidTabs(AnchorLayout): ''' The AndroidTabs class. You can use it to create your own custom tabbed panel. ''' default_tab = NumericProperty(0) ''' Index of the default tab. Default to 0. ''' tab_bar_height = NumericProperty('48dp') ''' Height of the tab bar. ''' tab_indicator_anim = BooleanProperty(True) ''' Tab indicator animation. Default to True. If you do not want animation set it to False. ''' tab_indicator_height = NumericProperty('2dp') ''' Height of the tab indicator. ''' tab_indicator_color = VariableListProperty([1]) ''' Color of the tab indicator. ''' anim_duration = NumericProperty(0.2) ''' Duration of the slide animation. Default to 0.2. ''' anim_threshold = BoundedNumericProperty(0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0) ''' Animation threshold allow you to change the tab indicator animation effect. Default to 0.8. ''' def on_carousel_index(self, carousel, index): # when the index of the carousel change, update # tab indicator, select the current tab and reset threshold data. current_tab_label = carousel.current_slide.tab_label if current_tab_label.state == 'normal': current_tab_label._do_press() self.tab_bar.update_indicator(current_tab_label.x, current_tab_label.width) def add_widget(self, widget): # You can add only subclass of AndroidTabsBase. if len(self.children) >= 2: if not issubclass(widget.__class__, AndroidTabsBase): raise AndroidTabsException( 'AndroidTabs accept only subclass of AndroidTabsBase') widget.tab_label.tab_bar = self.tab_bar self.tab_bar.layout.add_widget(widget.tab_label) self.carousel.add_widget(widget) return return super(AndroidTabs, self).add_widget(widget) def remove_widget(self, widget): # You can remove only subclass of AndroidTabsBase. if not issubclass(widget.__class__, AndroidTabsBase): raise AndroidTabsException( 'AndroidTabs can remove only subclass of AndroidTabBase') if widget.parent.parent == self.carousel: self.tab_bar.layout.remove_widget(widget.tab_label) self.carousel.remove_widget(widget)
class BackgroundColorBehavior(CommonElevationBehavior): background = StringProperty() """ Background image path. :attr:`background` is a :class:`~kivy.properties.StringProperty` and defaults to `None`. """ r = BoundedNumericProperty(1.0, min=0.0, max=1.0) """ The value of ``red`` in the ``rgba`` palette. :attr:`r` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `1.0`. """ g = BoundedNumericProperty(1.0, min=0.0, max=1.0) """ The value of ``green`` in the ``rgba`` palette. :attr:`g` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `1.0`. """ b = BoundedNumericProperty(1.0, min=0.0, max=1.0) """ The value of ``blue`` in the ``rgba`` palette. :attr:`b` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `1.0`. """ a = BoundedNumericProperty(0.0, min=0.0, max=1.0) """ The value of ``alpha channel`` in the ``rgba`` palette. :attr:`a` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `0.0`. """ radius = VariableListProperty([0], length=4) """ Canvas radius. .. code-block:: python # Top left corner slice. MDBoxLayout: md_bg_color: app.theme_cls.primary_color radius: [25, 0, 0, 0] :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` and defaults to `[0, 0, 0, 0]`. """ md_bg_color = ReferenceListProperty(r, g, b, a) """ The background color of the widget (:class:`~kivy.uix.widget.Widget`) that will be inherited from the :attr:`BackgroundColorBehavior` class. For example: .. code-block:: kv Widget: canvas: Color: rgba: 0, 1, 1, 1 Rectangle: size: self.size pos: self.pos similar to code: .. code-block:: kv <MyWidget@BackgroundColorBehavior> md_bg_color: 0, 1, 1, 1 :attr:`md_bg_color` is an :class:`~kivy.properties.ReferenceListProperty` and defaults to :attr:`r`, :attr:`g`, :attr:`b`, :attr:`a`. """ angle = NumericProperty(0) background_origin = ListProperty(None) _background_x = NumericProperty(0) _background_y = NumericProperty(0) _background_origin = ReferenceListProperty( _background_x, _background_y, ) def __init__(self, **kwarg): super().__init__(**kwarg) self.bind(pos=self.update_background_origin) def update_background_origin(self, *args): if self.background_origin: self._background_origin = self.background_origin else: self._background_origin = self.center
class PrinterStatusContent(BoxLayout): """ """ filename = StringProperty("") status = StringProperty("None") extruder_one_temp = NumericProperty(0) extruder_one_max_temp = NumericProperty(200) extruder_two_max_temp = NumericProperty(200) extruder_two_temp = NumericProperty(0) bed_max_temp = NumericProperty(200) bed_temp = NumericProperty(0) progress = StringProperty('') progress_number = NumericProperty(0) startup = False safety_counter = 0 update_lock = False etr = StringProperty("Error") te = StringProperty("Error") etr_start_time = -999 te_start_time = -999 first_round = True progress_width = NumericProperty(200) printing = True tae_x = NumericProperty(0) tae_y = NumericProperty(0) triangle_y = -150 grey_color = [0.50390625, 0.49609375, 0.49609375, 1] black_color = [0.0, 0.0, 0.0, 1.0] white_color = [1, 1, 1, 1] green_color = [0, 1, 0, 1] error_color = VariableListProperty([0.0, 0.0, 0.0, 1.0], length=4) temp_color = VariableListProperty([0.0, 0.0, 0.0, 1.0], length=4) #temp and error message icons temp_icon = StringProperty('Icons/Icon_Buttons/Temperature.png') error_icon = StringProperty('Icons/failure_icon.png') def __init__(self, *args, **kwargs): super(PrinterStatusContent, self).__init__(*args, **kwargs) #get the model self.model = roboprinter.printer_instance._settings.get(['Model']) #Logger.info('===================> model: {}'.format(self.model)) self.splash_event = Clock.schedule_interval(self.turn_off_splash, .1) self.extruder = Temp_Control_Button() self.manual = Motor_Control_Button() Clock.schedule_interval(self.monitor_errors, 0.2) self.update_lock = False Clock.schedule_interval(self.safety, 1) Clock.schedule_interval(self.update, 0.2) #add the move tools function to a global space session_saver.saved['Move_Tools'] = self.move_tools_to def move_tools_to(self, content_space): if content_space == "ERROR": if self.model == "Robo R2": self.tae_x = -800 else: #self.tae_x = -720 self.tae_x = -800 self.error_color = self.grey_color self.temp_color = self.black_color self.canvas.ask_update() elif content_space == "TEMP": self.tae_x = 0 self.error_color = self.black_color self.temp_color = self.grey_color self.canvas.ask_update() def add_error_box(self): #add the error container error_detection = Error_Detection() error_content = self.ids.error_box error_content.clear_widgets() error_content.add_widget(error_detection) def update(self, dt): current_data = roboprinter.printer_instance._printer.get_current_data() is_printing = current_data['state']['flags']['printing'] is_paused = current_data['state']['flags']['paused'] if (is_printing or is_paused) and not self.printing: self.printing = True self.start_print(0) self.move_tools_to("TEMP") #Add Start and pause buttons extruder_buttons = self.ids.status_buttons extruder_buttons.clear_widgets() extruder_buttons.add_widget(StartPauseButton()) extruder_buttons.add_widget(CancelButton()) elif not is_printing and not is_paused and self.printing: self.printing = False self.end_print(0) self.move_tools_to("ERROR") #Add Motor Controls and Temp Controls extruder_buttons = self.ids.status_buttons extruder_buttons.clear_widgets() self.extruder = Temp_Control_Button() self.manual = Motor_Control_Button() extruder_buttons.add_widget(self.extruder) extruder_buttons.add_widget(self.manual) if self.first_round: self.first_round = False self.add_error_box() #Monitor Temperature if self.is_anything_hot(): #swap icons self.temp_icon = "Icons/Printer Status/red temp icon.png" else: self.temp_icon = "Icons/Icon_Buttons/Temperature.png" def is_anything_hot(self): temp1 = self.grab_target_and_actual('tool0') temp2 = self.grab_target_and_actual('tool1') bed = self.grab_target_and_actual('bed') temps = [temp1['target'], temp2['target'], bed['target']] for temp in temps: if temp > 0.0: return True return False def start_print(self, dt): try: #make new object for the printer print_screen = Print_Screen() #clear widgets from the screen content_space = self.ids.printer_content content_space.clear_widgets() content_space.clear_widgets() #add the print screen content_space.add_widget(print_screen) except Exception as e: Logger.info("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! " + str(e)) traceback.print_exc() def end_print(self, dt): try: #make new object for the printer idle_screen = Idle_Screen() #clear widgets from the screen content_space = self.ids.printer_content content_space.clear_widgets() content_space.clear_widgets() #add Idle Screen content_space.add_widget(idle_screen) except AttributeError as e: Logger.info("Error in End Print") Clock.schedule_once(self.end_print, 1) Logger.info("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! " + str(e)) traceback.print_exc() def monitor_errors(self, dt): #monitor for errors if 'current_error' in session_saver.saved: error = session_saver.saved['current_error'] if error == 'MAINBOARD' or error == 'FIRMWARE' or error == 'BED_DISCONNECT': #disable features self.extruder.button_state = True self.manual.button_state = True #swap error icon self.error_icon = "Icons/Printer Status/failure_icon_red.png" self.error_color_adapter = self.white_color elif error == 'DEFAULT': self.extruder.button_state = False self.manual.button_state = False self.error_icon = "Icons/check_icon.png" else: self.extruder.button_state = False self.manual.button_state = False #swap error icon self.error_icon = "Icons/failure_icon.png" self.error_color_adapter = self.white_color def grab_target_and_actual(self, tool): acceptable_tools = {'tool0': 'tool0', 'tool1': 'tool1', 'bed': 'bed'} actual = 0 target = 0 if tool in acceptable_tools: temps = roboprinter.printer_instance._printer.get_current_temperatures( ) if tool in temps: if 'actual' in temps[tool] and 'target' in temps[tool]: if temps[tool]['actual'] == None: actual = 0 target = 0 else: actual = temps[tool]['actual'] target = temps[tool]['target'] else: actual = 0 target = 0 return {'actual': actual, 'target': target} def detirmine_layout(self): printer_type = roboprinter.printer_instance._settings.global_get( ['printerProfiles', 'defaultProfile']) model = printer_type['model'] tool0 = False tool1 = False bed = False if printer_type['extruder']['count'] == 1: tool0 = True elif printer_type['extruder']['count'] > 1: tool0 = True tool1 = True if printer_type['heatedBed']: bed = True if tool0 and tool1 and bed: tool_0 = Tool_Status(roboprinter.lang.pack['Tool_Status']['Tool1'], "tool0") tool_1 = Tool_Status(roboprinter.lang.pack['Tool_Status']['Tool2'], "tool1") bed = Tool_Status(roboprinter.lang.pack['Tool_Status']['Bed'], "bed") self.ids.tools.add_widget(tool_0) self.ids.tools.add_widget(tool_1) self.ids.tools.add_widget(bed) elif tool0 and bed: tool_0 = Tool_Status(roboprinter.lang.pack['Tool_Status']['Tool1'], "tool0") bed = Tool_Status(roboprinter.lang.pack['Tool_Status']['Bed'], "bed") self.ids.tools.add_widget(tool_0) self.ids.tools.add_widget(Label()) self.ids.tools.add_widget(bed) elif tool0: tool_0 = Tool_Status(roboprinter.lang.pack['Tool_Status']['Tool1'], "tool0") self.ids.tools.add_widget(Label()) self.ids.tools.add_widget(tool_0) self.ids.tools.add_widget(Label()) else: Logger.info( "##################### TOOL STATUS ERROR #######################" ) def turn_off_splash(self, dt): temp1 = self.grab_target_and_actual('tool0') self.extruder_one_max_temp = temp1['target'] self.extruder_one_temp = temp1['actual'] temp2 = self.grab_target_and_actual('tool1') self.extruder_two_max_temp = temp2['target'] self.extruder_two_temp = temp2['actual'] bed = self.grab_target_and_actual('bed') self.bed_max_temp = bed['target'] self.bed_temp = bed['actual'] #turn off the splash screen if self.extruder_one_temp != 0 and self.startup == False: #Logger.info("Turning Off the Splash Screen!") self.detirmine_layout() #check for updates self.check_updates() #check for updates every hour Clock.schedule_interval(self.update_clock, 3600) self.startup = True return False def update_clock(self, dt): current_data = roboprinter.printer_instance._printer.get_current_data() is_printing = current_data['state']['flags']['printing'] is_paused = current_data['state']['flags']['paused'] if not is_printing and not is_paused: self.check_updates() def check_updates(self): self.updates = UpdateScreen(populate=False) self.updates.refresh_versions() installed = self.updates.get_installed_version() available = self.updates.get_avail_version() Logger.info("Available: " + available.encode('utf-8') + " Installed: " + installed.encode('utf-8')) if installed < available and available != roboprinter.lang.pack[ 'Update_Printer']['Connection_Error'] and not self.update_lock: self.update_lock = True #updater popup update = Update_Warning_Popup(self.run_update, self.unlock_updater) update.open() def run_update(self): self.updates.run_updater() def unlock_updater(self): self.update_lock = False def safety(self, dt): self.safety_counter += 1 safety_time = 60 if self.safety_counter == safety_time and self.startup == False: self.detirmine_layout() self.check_updates() Clock.schedule_interval(self.update_clock, 3600) Clock.unschedule(self.splash_event) return False elif self.safety_counter == safety_time and self.startup == True: return False
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 it's 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 0. ''' 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 0. ''' 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({}) '''List of minimum sizes for each column. .. versionadded:: 1.0.7 :attr:`cols_minimum` is a :class:`~kivy.properties.DictProperty` and defaults to {}. ''' rows_minimum = DictProperty({}) '''List of minimum sizes for each row. .. versionadded:: 1.0.7 :attr:`rows_minimum` is a :class:`~kivy.properties.DictProperty` and defaults to {}. ''' minimum_width = NumericProperty(0) '''Minimum width needed to contain all children. .. versionadded:: 1.0.8 :attr:`minimum_width` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_height = NumericProperty(0) '''Minimum height needed to contain all children. .. versionadded:: 1.0.8 :attr:`minimum_height` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''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. ''' def __init__(self, **kwargs): self._cols = self._rows = None super(GridLayout, self).__init__(**kwargs) fbind = self.fast_bind 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) 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!') def update_minimum_size(self, *largs): # 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 children = self.children len_children = len(children) # 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 None if current_cols is None: current_cols = int(ceil(len_children / float(current_rows))) elif current_rows is None: current_rows = int(ceil(len_children / float(current_cols))) current_cols = max(1, current_cols) current_rows = max(1, current_rows) cols = [self.col_default_width] * current_cols cols_sh = [None] * current_cols rows = [self.row_default_height] * current_rows rows_sh = [None] * current_rows # update minimum size from the dicts # FIXME index might be outside the bounds ? for index, value in self.cols_minimum.items(): cols[index] = value for index, value in self.rows_minimum.items(): rows[index] = value # calculate minimum size for each columns and rows i = len_children - 1 for row in range(current_rows): for col in range(current_cols): # don't go further is we don't have child left if i < 0: break # get initial information from the child c = children[i] shw = c.size_hint_x shh = c.size_hint_y w = c.width h = c.height # 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 shh is None: rows[row] = nmax(rows[row], h) else: rows_sh[row] = nmax(rows_sh[row], shh) # next child i = i - 1 # calculate minimum width/height needed, starting from padding + # spacing padding_x = self.padding[0] + self.padding[2] padding_y = self.padding[1] + self.padding[3] spacing_x, spacing_y = self.spacing width = padding_x + spacing_x * (current_cols - 1) height = padding_y + spacing_y * (current_rows - 1) # then add the cell size width += sum(cols) height += sum(rows) # remember for layout self._cols = cols self._rows = rows self._cols_sh = cols_sh self._rows_sh = rows_sh # finally, set the minimum size self.minimum_size = (width, height) def do_layout(self, *largs): self.update_minimum_size() if self._cols is None: return if self.cols is None and self.rows is None: raise GridLayoutException('Need at least cols or rows constraint.') children = self.children len_children = len(children) if len_children == 0: return # speedup padding_left = self.padding[0] padding_top = self.padding[1] spacing_x, spacing_y = self.spacing selfx = self.x 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 else: cols = self._cols[:] cols_sh = self._cols_sh cols_weigth = sum([x for x in cols_sh if x]) strech_w = max(0, selfw - self.minimum_width) for index in range(len(cols)): # if the col don't have strech information, nothing to do col_stretch = cols_sh[index] if col_stretch is None: continue # calculate the column stretch, and take the maximum from # minimum size and the calculated stretch col_width = cols[index] col_width = max(col_width, strech_w * col_stretch / cols_weigth) cols[index] = col_width # 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 else: rows = self._rows[:] rows_sh = self._rows_sh rows_weigth = sum([x for x in rows_sh if x]) strech_h = max(0, selfh - self.minimum_height) for index in range(len(rows)): # if the row don't have strech information, nothing to do row_stretch = rows_sh[index] if row_stretch is None: continue # calculate the row stretch, and take the maximum from minimum # size and the calculated stretch row_height = rows[index] row_height = max(row_height, strech_h * row_stretch / rows_weigth) rows[index] = row_height # reposition every child i = len_children - 1 y = self.top - padding_top for row_height in rows: x = selfx + padding_left for col_width in cols: if i < 0: break c = children[i] c.x = x c.y = y - row_height c.width = col_width c.height = row_height i = i - 1 x = x + col_width + spacing_x y -= row_height + spacing_y
class CommonElevationBehavior(Widget): """Common base class for rectangular and circular elevation behavior.""" elevation = BoundedNumericProperty(0, min=0, errorvalue=0) """ Elevation of the widget. .. note:: Although, this value does not represent the current elevation of the widget. :attr:`~CommonElevationBehavior._elevation` can be used to animate the current elevation and come back using the :attr:`~CommonElevationBehavior.elevation` property directly. For example: .. code-block:: python from kivy.lang import Builder from kivy.uix.behaviors import ButtonBehavior from kivymd.app import MDApp from kivymd.uix.behaviors import CircularElevationBehavior, CircularRippleBehavior from kivymd.uix.boxlayout import MDBoxLayout KV = ''' #:import Animation kivy.animation.Animation <WidgetWithShadow> size_hint: [None, None] elevation: 6 animation_: None md_bg_color: [1] * 4 on_size: self.radius = [self.height / 2] * 4 on_press: if self.animation_: \ self.animation_.cancel(self); \ self.animation_ = Animation(_elevation=self.elevation + 6, d=0.08); \ self.animation_.start(self) on_release: if self.animation_: \ self.animation_.cancel(self); \ self.animation_ = Animation(_elevation = self.elevation, d=0.08); \ self.animation_.start(self) MDFloatLayout: WidgetWithShadow: size: [root.size[1] / 2] * 2 pos_hint: {"center": [0.5, 0.5]} ''' class WidgetWithShadow( CircularElevationBehavior, CircularRippleBehavior, ButtonBehavior, MDBoxLayout, ): def __init__(self, **kwargs): # always set the elevation before the super().__init__ call # self.elevation = 6 super().__init__(**kwargs) def on_size(self, *args): self.radius = [self.size[0] / 2] class Example(MDApp): def build(self): return Builder.load_string(KV) Example().run() """ # Shadow rendering properties. # Shadow rotation memory - SHARED ACROSS OTHER CLASSES. angle = NumericProperty(0) """ Angle of rotation in degrees of the current shadow. This value is shared across different widgets. .. note:: This value will affect both, hard and soft shadows. Each shadow has his own origin point that's computed every time the elevation changes. .. warning:: Do not add `PushMatrix` inside the canvas before and add `PopMatrix` in the next layer, this will cause visual errors, because the stack used will clip the push and pop matrix already inside the canvas.before canvas layer. Incorrect: .. code-block:: kv <TiltedWidget> canvas.before: PushMatrix [...] canvas: PopMatrix Correct: .. code-block:: kv <TiltedWidget> canvas.before: PushMatrix [...] PopMatrix :attr:`angle` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ radius = VariableListProperty([0]) """ Radius of the corners of the shadow. This values represents each corner of the shadow, starting from `top-left` corner and going clockwise. .. code-block:: python radius = [ "top-left", "top-right", "bottom-right", "bottom-left", ] This value can be expanded thus allowing this settings to be valid: .. code-block:: python widget.radius=[0] # Translates to [0, 0, 0, 0] widget.radius=[10, 3] # Translates to [10, 3, 10, 3] widget.radius=[7.0, 8.7, 1.5, 3.0] # Translates to [7, 8, 1, 3] .. note:: This value will affect both, hard and soft shadows. This value only affects :class:`~RoundedRectangularElevationBehavior` for now, but can be stored and used by custom shadow draw functions. :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` and defaults to `[0, 0, 0, 0]`. """ # Position of the shadow. _shadow_origin_x = NumericProperty(0) """ Shadow origin `x` position for the rotation origin. Managed by `_shadow_origin`. :attr:`_shadow_origin_x` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. .. note:: This property is automatically processed. by _shadow_origin. """ _shadow_origin_y = NumericProperty(0) """ Shadow origin y position for the rotation origin. Managed by :attr:`_shadow_origin`. :attr:`_shadow_origin_y` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. .. note:: This property is automatically processed. """ _shadow_origin = ReferenceListProperty(_shadow_origin_x, _shadow_origin_y) """ Soft shadow rotation origin point. :attr:`_shadow_origin` is an :class:`~kivy.properties.ReferenceListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed and relative to the canvas center. """ _shadow_pos = ListProperty([0, 0]) # custom offset """ Soft shadow origin point. :attr:`_shadow_pos` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed and relative to the widget's canvas center. """ shadow_pos = ListProperty([0, 0]) # bottom left corner """ Custom shadow origin point. If this property is set, :attr:`_shadow_pos` will be ommited. This property allows users to fake light source. :attr:`shadow_pos` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: this value overwrite the :attr:`_shadow_pos` processing. """ # Shadow Group shared memory __shadow_groups = {"global": []} shadow_group = StringProperty("global") """ Widget's shadow group. By default every widget with a shadow is saved inside the memory :attr:`__shadow_groups` as a weakref. This means that you can have multiple light sources, one for every shadow group. To fake a light source use :attr:`force_shadow_pos`. :attr:`shadow_group` is an :class:`~kivy.properties.StringProperty` and defaults to `"global"`. """ _elevation = BoundedNumericProperty(0, min=0, errorvalue=0) """ Current elevation of the widget. .. warning:: This property is the current elevation of the widget, do not use this property directly, instead, use :class:`~CommonElevationBehavior` elevation. :attr:`_elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ # soft shadow _soft_shadow_texture = ObjectProperty() """ Texture of the soft shadow texture for the canvas. :attr:`_soft_shadow_texture` is an :class:`~kivy.core.image.Image` and defaults to `None`. .. note:: This property is automatically processed. """ soft_shadow_size = ListProperty([0, 0]) """ Size of the soft shadow texture over the canvas. :attr:`soft_shadow_size` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed. """ soft_shadow_pos = ListProperty([0, 0]) """ Position of the hard shadow texture over the canvas. :attr:`soft_shadow_pos` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed. """ soft_shadow_cl = ListProperty([0, 0, 0, 0.50]) """ Color of the soft shadow. :attr:`soft_shadow_cl` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0, 0, 0.15]`. """ # hard shadow hard_shadow_texture = ObjectProperty() """ Texture of the hard shadow texture for the canvas. :attr:`hard_shadow_texture` is an :class:`~kivy.core.image.Image` and defaults to `None`. .. note:: This property is automatically processed when elevation is changed. """ hard_shadow_size = ListProperty([0, 0]) """ Size of the hard shadow texture over the canvas. :attr:`hard_shadow_size` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed when elevation is changed. """ hard_shadow_pos = ListProperty([0, 0]) """ Position of the hard shadow texture over the canvas. :attr:`hard_shadow_pos` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0]`. .. note:: This property is automatically processed when elevation is changed. """ hard_shadow_cl = ListProperty([0, 0, 0, 0.15]) """ Color of the hard shadow. .. note:: :attr:`hard_shadow_cl` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0, 0, 0.15]`. """ # Shared property for some calculations. # This values are used to improve the gaussain blur and avoid that # the blur goes outside the texture. hard_shadow_offset = BoundedNumericProperty( 2, min=0, errorhandler=lambda x: 0 if x < 0 else x ) """ This value sets a special offset to the shadow canvas, this offset allows a correct draw of the canvas size. allowing the effect to correctly blur the image in the given space. :attr:`hard_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `2`. """ soft_shadow_offset = BoundedNumericProperty( 4, min=0, errorhandler=lambda x: 0 if x < 0 else x ) """ This value sets a special offset to the shadow canvas, this offset allows a correct draw of the canvas size. allowing the effect to correctly blur the image in the given space. :attr:`soft_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `4`. """ draw_shadow = ObjectProperty(None) """ This property controls the draw call of the context. This property is automatically set to :attr:`__draw_shadow__` inside the `super().__init__ call.` unless the property is different of None. To set a different drawing instruction function, set this property before the `super(),__init__` call inside the `__init__` definition of the new class. You can use the source for this classes as example of how to draw over with the context: Real time shadows: #. :class:`~RectangularElevationBehavior` #. :class:`~CircularElevationBehavior` #. :class:`~RoundedRectangularElevationBehavior` #. :class:`~ObservableShadow` Fake shadows (d`ont use this property): #. :class:`~FakeRectangularElevationBehavior` #. :class:`~FakeCircularElevationBehavior` :attr:`draw_shadow` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. .. note:: If this property is left to `None` the :class:`~CommonElevationBehavior` will set to a function that will raise a `NotImplementedError` inside `super().__init__`. Follow the next example to set a new draw instruction for the class inside `__init__`: .. code-block:: python class RoundedRectangularElevationBehavior(CommonElevationBehavior): ''' Shadow class for the RoundedRectangular shadow behavior. Controls the size and position of the shadow. ''' def __init__(self, **kwargs): self._draw_shadow = WeakMethod(self.__draw_shadow__) super().__init__(**kwargs) def __draw_shadow__(self, origin, end, context=None): context.draw(...) Context is a `Pillow` `ImageDraw` class. For more information check the [Pillow official documentation](https://github.com/python-pillow/Pillow/). """ # All classes that uses a fake shadow shall set this value as `True` # for performance. _fake_elevation = BooleanProperty(False) def __init__(self, **kwargs): if self.draw_shadow is None: self.draw_shadow = WeakMethod(self.__draw_shadow__) self.prev_shadow_group = None im = BytesIO() Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") im.seek(0) # Setting a empty image as texture, improves performance. self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( im, ext="png" ).texture Clock.schedule_once(self.shadow_preset, -1) self.on_shadow_group(self, self.shadow_group) self.bind( pos=self._update_shadow, size=self._update_shadow, radius=self._update_shadow, ) super().__init__(**kwargs) def on_shadow_group(self, instance, value): """ This function controls the shadow group of the widget. Do not use Directly to change the group. instead, use the shadow_group :attr:`property`. """ groups = CommonElevationBehavior.__shadow_groups if self.prev_shadow_group: group = groups[self.prev_shadow_group] for widget in group[:]: if widget() is self: group.remove(widget) group = self.prev_shadow_group = self.shadow_group if group not in groups: groups[group] = [] r = ref(self, CommonElevationBehavior._clear_shadow_groups) groups[group].append(r) @staticmethod def _clear_shadow_groups(wk): # auto flush the element when the weak reference have been deleted groups = CommonElevationBehavior.__shadow_groups for group in list(groups.values()): if not group: break if wk in group: group.remove(wk) break def force_shadow_pos(self, shadow_pos): """ This property forces the shadow position in every widget inside the widget. The argument :attr:`shadow_pos` is expected as a <class 'list'> or <class 'tuple'>. """ if self.shadow_group is None: return group = CommonElevationBehavior.__shadow_groups[self.shadow_group] for wk in group[:]: widget = wk() if widget is None: group.remove(wk) widget.shadow_pos = shadow_pos del group def update_group_property(self, property_name, value): """ This functions allows to change properties of every widget inside the shadow group. """ if self.shadow_group is None: return group = CommonElevationBehavior.__shadow_groups[self.shadow_group] for wk in group[:]: widget = wk() if widget is None: group.remove(wk) setattr(widget, property_name, value) del group def shadow_preset(self, *args): """ This function is meant to set the default configuration of the elevation. After a new instance is created, the elevation property will be launched and thus this function will update the elevation if the KV lang have not done it already. Works similar to an `__after_init__` call inside a widget. """ if self.elevation is None: self.elevation = 10 if self._fake_elevation is False: self._update_shadow(self, self.elevation) self.bind( pos=self._update_shadow, size=self._update_shadow, _elevation=self._update_shadow, ) def on_elevation(self, instance, value): """ Elevation event that sets the current elevation value to `_elevation`. """ if value is not None: self._elevation = value def _set_soft_shadow_a(self, value): value = 0 if value < 0 else (1 if value > 1 else value) self.soft_shadow_cl[-1] = value return True def _set_hard_shadow_a(self, value): value = 0 if value < 0 else (1 if value > 1 else value) self.hard_shadow_cl[-1] = value return True def _get_soft_shadow_a(self): return self.soft_shadow_cl[-1] def _get_hard_shadow_a(self): return self.hard_shadow_cl[-1] _soft_shadow_a = AliasProperty( _get_soft_shadow_a, _set_soft_shadow_a, bind=["soft_shadow_cl"] ) _hard_shadow_a = AliasProperty( _get_hard_shadow_a, _set_hard_shadow_a, bind=["hard_shadow_cl"] ) def on_disabled(self, instance, value): """ This function hides the shadow when the widget is disabled. It sets the shadow to `0`. """ if self.disabled is True: self._elevation = 0 else: self._elevation = 0 if self.elevation is None else self.elevation self._update_shadow(self, self._elevation) try: super().on_disabled(instance, value) except Exception: pass def _update_elevation(self, instance, value): self._elevation = value self._update_shadow(instance, value) def _update_shadow_pos(self, instance, value): if self._elevation > 0: self.hard_shadow_pos = [ self.x - dp(self.hard_shadow_offset), # + self.shadow_pos[0], self.y - dp(self.hard_shadow_offset), # + self.shadow_pos[1], ] if self.shadow_pos == [0, 0]: self.soft_shadow_pos = [ self.x + self._shadow_pos[0] - self._elevation - dp(self.soft_shadow_offset), self.y + self._shadow_pos[1] - self._elevation - dp(self.soft_shadow_offset), ] else: self.soft_shadow_pos = [ self.x + self.shadow_pos[0] - self._elevation - dp(self.soft_shadow_offset), self.y + self.shadow_pos[1] - self._elevation - dp(self.soft_shadow_offset), ] self._shadow_origin = [ self.soft_shadow_pos[0] + self.soft_shadow_size[0] / 2, self.soft_shadow_pos[1] + self.soft_shadow_size[1] / 2, ] def on__shadow_pos(self, ins, val): """ Updates the shadow with the computed value. Call this function every time you need to force a shadow update. """ self._update_shadow_pos(ins, val) def on_shadow_pos(self, ins, val): """ Updates the shadow with the fixed value. Call this function every time you need to force a shadow update. """ self._update_shadow_pos(ins, val) def _update_shadow(self, instance, value): self._update_shadow_pos(instance, value) if self._elevation > 0 and self._fake_elevation is False: # dynamic elevation position for the shadow if self.shadow_pos == [0, 0]: self._shadow_pos = [0, -self._elevation * 0.4] # HARD Shadow offset = int(dp(self.hard_shadow_offset)) size = [ int(self.size[0] + (offset * 2)), int(self.size[1] + (offset * 2)), ] im = BytesIO() # context img = Image.new("RGBA", tuple(size), color=(0, 0, 0, 0)) # draw context shadow = ImageDraw.Draw(img) self.draw_shadow()( [offset, offset], [ int(size[0] - 1 - offset), int(size[1] - 1 - offset), ], context=shadow # context=ref(shadow) ) img = img.filter( ImageFilter.GaussianBlur( radius=int(dp(1 + self.hard_shadow_offset / 3)) ) ) img.save(im, format="png") im.seek(0) self.hard_shadow_size = size self.hard_shadow_texture = CoreImage(im, ext="png").texture # soft shadow if self.soft_shadow_cl[-1] > 0: offset = dp(self.soft_shadow_offset) size = [ int(self.size[0] + dp(self._elevation * 2) + (offset * 2)), int(self.size[1] + dp(self._elevation * 2) + (offset * 2)), # ((self._elevation)*2) + x + (offset*2)) for x in self.size ] im = BytesIO() img = Image.new("RGBA", tuple(size), color=((0,) * 4)) shadow = ImageDraw.Draw(img) _offset = int(dp(self._elevation + offset)) self.draw_shadow()( [ _offset, _offset, ], [int(size[0] - _offset - 1), int(size[1] - _offset - 1)], context=shadow # context=ref(shadow) ) img = img.filter( ImageFilter.GaussianBlur(radius=self._elevation // 2) ) shadow = ImageDraw.Draw(img) img.save(im, format="png") im.seek(0) self.soft_shadow_size = size self._soft_shadow_texture = CoreImage(im, ext="png").texture else: im = BytesIO() Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") im.seek(0) self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( im, ext="png" ).texture return def _get_center(self): center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] return center def __draw_shadow__(self, origin, end, context=None): raise NotImplementedError( "KivyMD:\n" "If you see this error, this means that either youre using " "`CommonElevationBehavio`r directly or your 'shader' dont have a " "`_draw_shadow` instruction, remember to overwrite this function" "to draw over the image context. the figure you would like." )
class MDDropdownMenu(ThemableBehavior, FloatLayout): """ :Events: `on_release` The method that will be called when you click menu items. """ header_cls = ObjectProperty() """ An instance of the class (`Kivy` or `KivyMD` widget) that will be added to the menu header. .. versionadded:: 0.104.2 See Header_ for more information. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-header-cls.png :align: center :attr:`header_cls` is a :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ items = ListProperty() """ See :attr:`~kivy.uix.recycleview.RecycleView.data`. .. code-block:: python items = [ { "viewclass": "OneLineListItem", "height": dp(56), "text": f"Item {i}", } for i in range(5) ] self.menu = MDDropdownMenu( items=items, ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-items.png :align: center :attr:`items` is a :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ width_mult = NumericProperty(1) """ This number multiplied by the standard increment ('56dp' on mobile, '64dp' on desktop), determines the width of the menu items. If the resulting number were to be too big for the application Window, the multiplier will be adjusted for the biggest possible one. .. code-block:: python self.menu = MDDropdownMenu( width_mult=4, ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-width-mult-4.png :align: center .. code-block:: python self.menu = MDDropdownMenu( width_mult=8, ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-width-mult-8.png :align: center :attr:`width_mult` is a :class:`~kivy.properties.NumericProperty` and defaults to `1`. """ max_height = NumericProperty() """ The menu will grow no bigger than this number. Set to 0 for no limit. .. code-block:: python self.menu = MDDropdownMenu( max_height=dp(112), ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-max-height-112.png :align: center .. code-block:: python self.menu = MDDropdownMenu( max_height=dp(224), ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-max-height-224.png :align: center :attr:`max_height` is a :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ border_margin = NumericProperty("4dp") """ Margin between Window border and menu. .. code-block:: python self.menu = MDDropdownMenu( border_margin=dp(4), ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-border-margin-4.png :align: center .. code-block:: python self.menu = MDDropdownMenu( border_margin=dp(24), ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-border-margin-24.png :align: center :attr:`border_margin` is a :class:`~kivy.properties.NumericProperty` and defaults to `4dp`. """ ver_growth = OptionProperty(None, allownone=True, options=["up", "down"]) """ Where the menu will grow vertically to when opening. Set to `None` to let the widget pick for you. Available options are: `'up'`, `'down'`. .. code-block:: python self.menu = MDDropdownMenu( ver_growth="up", ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-ver-growth-up.gif :align: center .. code-block:: python self.menu = MDDropdownMenu( ver_growth="down", ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-ver-growth-down.gif :align: center :attr:`ver_growth` is a :class:`~kivy.properties.OptionProperty` and defaults to `None`. """ hor_growth = OptionProperty(None, allownone=True, options=["left", "right"]) """ Where the menu will grow horizontally to when opening. Set to `None` to let the widget pick for you. Available options are: `'left'`, `'right'`. .. code-block:: python self.menu = MDDropdownMenu( hor_growth="left", ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-hor-growth-left.gif :align: center .. code-block:: python self.menu = MDDropdownMenu( hor_growth="right", ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-hor-growth-right.gif :align: center :attr:`hor_growth` is a :class:`~kivy.properties.OptionProperty` and defaults to `None`. """ background_color = ColorProperty(None) """ Color of the background of the menu. .. code-block:: python self.menu = MDDropdownMenu( background_color=self.theme_cls.primary_light, ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-background-color.png :align: center :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ opening_transition = StringProperty("out_cubic") """ Type of animation for opening a menu window. :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` and defaults to `'out_cubic'`. """ opening_time = NumericProperty(0.2) """ Menu window opening animation time and you can set it to 0 if you don't want animation of menu opening. :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.2`. """ caller = ObjectProperty() """ The widget object that calls the menu window. :attr:`caller` is a :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ position = OptionProperty("auto", options=["top", "auto", "center", "bottom"]) """ Menu window position relative to parent element. Available options are: `'auto'`, `'center'`, `'bottom'`. See Position_ for more information. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-position.png :align: center :attr:`position` is a :class:`~kivy.properties.OptionProperty` and defaults to `'auto'`. """ radius = VariableListProperty([dp(7)]) """ Menu radius. .. code-block:: python self.menu = MDDropdownMenu( radius=[24, 0, 24, 0], ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-radius.png :align: center :attr:`radius` is a :class:`~kivy.properties.VariableListProperty` and defaults to `'[dp(7)]'`. """ elevation = NumericProperty(10) """ Elevation value of menu dialog. .. versionadded:: 1.0.0 .. code-block:: python self.menu = MDDropdownMenu( elevation=16, ..., ) .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-elevation.png :align: center :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to `10`. """ _start_coords = [] _calculate_complete = False _calculate_process = False def __init__(self, **kwargs): super().__init__(**kwargs) Window.bind(on_resize=self.check_position_caller) Window.bind(on_maximize=self.set_menu_properties) Window.bind(on_restore=self.set_menu_properties) Clock.schedule_once(self.ajust_radius) self.register_event_type("on_dismiss") self.menu = self.ids.md_menu self.target_height = 0 def check_position_caller(self, instance_window: WindowSDL, width: int, height: int) -> None: """Called when the application root window is resized.""" # FIXME: Menu position is not recalculated when changing the size of # the root application window. self.set_menu_properties(0) def set_menu_properties(self, interval: Union[int, float] = 0) -> None: """Sets the size and position for the menu window.""" if self.caller: self.ids.md_menu.data = self.items # We need to pick a starting point, see how big we need to be, # and where to grow to. self._start_coords = self.caller.to_window(self.caller.center_x, self.caller.center_y) self.target_width = self.width_mult * m_res.STANDARD_INCREMENT # If we're wider than the Window... if self.target_width > Window.width: # ...reduce our multiplier to max allowed. self.target_width = ( int(Window.width / m_res.STANDARD_INCREMENT) * m_res.STANDARD_INCREMENT) # Set the target_height of the menu depending on the size of # each MDMenuItem or MDMenuItemIcon. self.target_height = 0 for item in self.ids.md_menu.data: self.target_height += item.get("height", dp(72)) # If we're over max_height... if 0 < self.max_height < self.target_height: self.target_height = self.max_height # Establish vertical growth direction. if self.ver_growth is not None: ver_growth = self.ver_growth else: # If there's enough space below us: if (self.target_height <= self._start_coords[1] - self.border_margin): ver_growth = "down" # if there's enough space above us: elif (self.target_height < Window.height - self._start_coords[1] - self.border_margin): ver_growth = "up" # Otherwise, let's pick the one with more space and adjust # ourselves. else: # If there"s more space below us: if (self._start_coords[1] >= Window.height - self._start_coords[1]): ver_growth = "down" self.target_height = (self._start_coords[1] - self.border_margin) # If there's more space above us: else: ver_growth = "up" self.target_height = (Window.height - self._start_coords[1] - self.border_margin) if self.hor_growth is not None: hor_growth = self.hor_growth else: # If there's enough space to the right: if (self.target_width <= Window.width - self._start_coords[0] - self.border_margin): hor_growth = "right" # if there's enough space to the left: elif (self.target_width < self._start_coords[0] - self.border_margin): hor_growth = "left" # Otherwise, let's pick the one with more space and adjust # ourselves. else: # if there"s more space to the right: if (Window.width - self._start_coords[0] >= self._start_coords[0]): hor_growth = "right" self.target_width = (Window.width - self._start_coords[0] - self.border_margin) # if there"s more space to the left: else: hor_growth = "left" self.target_width = (self._start_coords[0] - self.border_margin) if ver_growth == "down": self.tar_y = self._start_coords[1] - self.target_height else: # should always be "up" self.tar_y = self._start_coords[1] if hor_growth == "right": self.tar_x = self._start_coords[0] else: # should always be "left" self.tar_x = self._start_coords[0] - self.target_width self._calculate_complete = True def ajust_radius(self, interval: Union[int, float]) -> None: """ Adjusts the radius of the first and last items in the menu list according to the radius that is set for the menu. """ radius_for_firt_item = self.radius[:2] radius_for_last_item = self.radius[2:] firt_data_item = self.items[0] last_data_item = self.items[-1] firt_data_item["radius"] = radius_for_firt_item + [0, 0] last_data_item["radius"] = [0, 0] + radius_for_last_item last_data_item["divider"] = None self.items[0] = firt_data_item self.items[-1] = last_data_item # For all other elements of the list, except for the first and last, # we set the value of the radius to `0`. for i, data_item in enumerate(self.items): if "radius" not in data_item: data_item["radius"] = 0 self.items[i] = data_item def adjust_position(self) -> str: """ Returns value 'auto' for the menu position if the menu position is out of screen. """ target_width = self.target_width target_height = self.target_height caller = self.caller position = self.position if (caller.x < target_width or caller.x + target_width > Window.width or caller.y + target_height > Window.height or (caller.y < target_height and position == "center")): position = "auto" if self.hor_growth or self.ver_growth: self.hor_growth = None self.ver_growth = None self.set_menu_properties() return position def open(self) -> None: """Animate the opening of a menu window.""" def open(interval): if not self._calculate_complete: return position = self.adjust_position() if position == "auto": self.menu.pos = self._start_coords anim = Animation( x=self.tar_x, y=self.tar_y - (self.header_cls.height if self.header_cls else 0), width=self.target_width, height=self.target_height, duration=self.opening_time, opacity=1, transition=self.opening_transition, ) anim.start(self.menu) else: if position == "center": self.menu.pos = ( self._start_coords[0] - self.target_width / 2, self._start_coords[1] - self.target_height / 2, ) elif position == "bottom": self.menu.pos = ( self._start_coords[0] - self.target_width / 2, self.caller.pos[1] - self.target_height, ) elif position == "top": self.menu.pos = ( self._start_coords[0] - self.target_width / 2, self.caller.pos[1] + self.caller.height, ) anim = Animation( width=self.target_width, height=self.target_height, duration=self.opening_time, opacity=1, transition=self.opening_transition, ) anim.start(self.menu) Window.add_widget(self) Clock.unschedule(open) self._calculate_process = False self.set_menu_properties() if not self._calculate_process: self._calculate_process = True Clock.schedule_interval(open, 0) def on_header_cls(self, instance_dropdown_menu, instance_user_menu_header) -> None: """Called when a value is set to the :attr:`header_cls` parameter.""" def add_content_header_cls(interval): self.ids.content_header.clear_widgets() self.ids.content_header.add_widget(instance_user_menu_header) Clock.schedule_once(add_content_header_cls, 1) def on_touch_down(self, touch): if not self.menu.collide_point(*touch.pos): self.dispatch("on_dismiss") return True super().on_touch_down(touch) return True def on_touch_move(self, touch): super().on_touch_move(touch) return True def on_touch_up(self, touch): super().on_touch_up(touch) return True def on_dismiss(self) -> None: """Called when the menu is closed.""" Window.remove_widget(self) self.menu.width = 0 self.menu.height = 0 self.menu.opacity = 0 def dismiss(self, *args) -> None: """Closes the menu.""" self.on_dismiss()
class StackLayout(Layout): '''Stack 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 single 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 it's children: [padding_left, padding_top, padding_right, padding_bottom]. padding also accepts a two argument form [padding_horizontal, padding_vertical] and a single argument form [padding]. .. versionchanged:: 1.7.0 Replaced the NumericProperty with a VariableListProperty. :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and defaults to [0, 0, 0, 0]. ''' 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'. .. versionchanged:: 1.5.0 :attr:`orientation` now correctly handles all valid combinations of 'lr','rl','tb','bt'. Before this version only 'lr-tb' and 'tb-lr' were supported, and 'tb-lr' was misnamed and placed widgets from bottom to top and from right to left (reversed compared to what was expected). .. note:: 'lr' means Left to Right. 'rl' means Right to Left. 'tb' means Top to Bottom. 'bt' means Bottom to Top. ''' minimum_width = NumericProperty(0) '''Minimum width needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_width` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_height = NumericProperty(0) '''Minimum height needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_height` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''Minimum size needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_size` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`minimum_width`, :attr:`minimum_height`) properties. ''' def __init__(self, **kwargs): super(StackLayout, self).__init__(**kwargs) trigger = self._trigger_layout fbind = self.fbind fbind('padding', trigger) fbind('spacing', trigger) fbind('children', trigger) fbind('orientation', trigger) fbind('size', trigger) fbind('pos', trigger) def do_layout(self, *largs): if not self.children: return # optimize layout by preventing looking at the same attribute in a loop selfpos = self.pos selfsize = self.size orientation = self.orientation.split('-') padding_left = self.padding[0] padding_top = self.padding[1] padding_right = self.padding[2] padding_bottom = self.padding[3] padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom spacing_x, spacing_y = self.spacing lc = [] # Determine which direction and in what order to place the widgets posattr = [0] * 2 posdelta = [0] * 2 posstart = [0] * 2 for i in (0, 1): posattr[i] = 1 * (orientation[i] in ('tb', 'bt')) k = posattr[i] if orientation[i] == 'lr': # left to right posdelta[i] = 1 posstart[i] = selfpos[k] + padding_left elif orientation[i] == 'bt': # bottom to top posdelta[i] = 1 posstart[i] = selfpos[k] + padding_bottom elif orientation[i] == 'rl': # right to left posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_right else: # top to bottom posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_top innerattr, outerattr = posattr ustart, vstart = posstart deltau, deltav = posdelta del posattr, posdelta, posstart u = ustart # inner loop position variable v = vstart # outer loop position variable # space calculation, used for determining when a row or column is full if orientation[0] in ('lr', 'rl'): sv = padding_y # size in v-direction, for minimum_size property su = padding_x # size in h-direction spacing_u = spacing_x spacing_v = spacing_y padding_u = padding_x padding_v = padding_y else: sv = padding_x # size in v-direction, for minimum_size property su = padding_y # size in h-direction spacing_u = spacing_y spacing_v = spacing_x padding_u = padding_y padding_v = padding_x # space calculation, row height or column width, for arranging widgets lv = 0 urev = (deltau < 0) vrev = (deltav < 0) firstchild = self.children[0] sizes = [] for c in reversed(self.children): if c.size_hint[outerattr]: c.size[outerattr] = max( 1, c.size_hint[outerattr] * (selfsize[outerattr] - padding_v)) # does the widget fit in the row/column? ccount = len(lc) totalsize = availsize = max( 0, selfsize[innerattr] - padding_u - spacing_u * ccount) if not lc: if c.size_hint[innerattr]: childsize = max(1, c.size_hint[innerattr] * totalsize) else: childsize = max(0, c.size[innerattr]) availsize = selfsize[innerattr] - padding_u - childsize testsizes = [childsize] else: testsizes = [0] * (ccount + 1) for i, child in enumerate(lc): if availsize <= 0: # no space left but we're trying to add another widget. availsize = -1 break if child.size_hint[innerattr]: testsizes[i] = childsize = max( 1, child.size_hint[innerattr] * totalsize) else: testsizes[i] = childsize = max(0, child.size[innerattr]) availsize -= childsize if c.size_hint[innerattr]: testsizes[-1] = max(1, c.size_hint[innerattr] * totalsize) else: testsizes[-1] = max(0, c.size[innerattr]) availsize -= testsizes[-1] if availsize >= 0 or not lc: # even if there's no space, we always add one widget to a row lc.append(c) sizes = testsizes lv = max(lv, c.size[outerattr]) continue # apply the sizes for i, child in enumerate(lc): if child.size_hint[innerattr]: child.size[innerattr] = sizes[i] # push the line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] c2.pos[innerattr] = u pos_outer = v if vrev: # v position is actually the top/right side of the widget # when going from high to low coordinate values, # we need to subtract the height/width from the position. pos_outer -= c2.size[outerattr] c2.pos[outerattr] = pos_outer if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u v += deltav * lv v += deltav * spacing_v lc = [c] lv = c.size[outerattr] if c.size_hint[innerattr]: sizes = [ max( 1, c.size_hint[innerattr] * (selfsize[innerattr] - padding_u)) ] else: sizes = [max(0, c.size[innerattr])] u = ustart if lc: # apply the sizes for i, child in enumerate(lc): if child.size_hint[innerattr]: child.size[innerattr] = sizes[i] # push the last (incomplete) line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] c2.pos[innerattr] = u pos_outer = v if vrev: pos_outer -= c2.size[outerattr] c2.pos[outerattr] = pos_outer if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u self.minimum_size[outerattr] = sv
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 0. ''' 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 0. ''' 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. ''' 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) 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!') 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 n_cols = len(cols) has_bound_y = has_bound_x = False for i, child in enumerate(reversed(self.children)): (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 row, col = divmod(i, n_cols) # 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): selfx = self.x padding_left = self.padding[0] padding_top = self.padding[1] spacing_x, spacing_y = self.spacing i = count - 1 y = self.top - padding_top cols = self._cols for row_height in self._rows: x = selfx + padding_left for col_width in cols: if i < 0: break yield i, x, y - row_height, col_width, row_height i = i - 1 x = x + col_width + spacing_x y -= row_height + spacing_y 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)
class BoxLayout(Layout): '''Box layout class. See module documentation for more information. ''' spacing = NumericProperty(0) '''Spacing between children, in pixels. :attr:`spacing` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between layout box and 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]. ''' orientation = OptionProperty('horizontal', options=('horizontal', 'vertical')) '''Orientation of the layout. :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and defaults to 'horizontal'. Can be 'vertical' or 'horizontal'. ''' def __init__(self, **kwargs): super(BoxLayout, self).__init__(**kwargs) self.bind(spacing=self._trigger_layout, padding=self._trigger_layout, children=self._trigger_layout, orientation=self._trigger_layout, parent=self._trigger_layout, size=self._trigger_layout, pos=self._trigger_layout) def do_layout(self, *largs): # optimize layout by preventing looking at the same attribute in a loop len_children = len(self.children) if len_children == 0: return selfx = self.x selfy = self.y selfw = self.width selfh = self.height padding_left = self.padding[0] padding_top = self.padding[1] padding_right = self.padding[2] padding_bottom = self.padding[3] spacing = self.spacing orientation = self.orientation padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom # calculate maximum space used by size_hint stretch_weight_x = 0. stretch_weight_y = 0. minimum_size_x = padding_x + spacing * (len_children - 1) minimum_size_y = padding_y + spacing * (len_children - 1) for w in self.children: shw = w.size_hint_x shh = w.size_hint_y if shw is None: minimum_size_x += w.width else: stretch_weight_x += shw if shh is None: minimum_size_y += w.height else: stretch_weight_y += shh if orientation == 'horizontal': x = padding_left stretch_space = max(0.0, selfw - minimum_size_x) for c in reversed(self.children): shw = c.size_hint_x shh = c.size_hint_y w = c.width h = c.height cx = selfx + x cy = selfy + padding_bottom if shw: w = stretch_space * shw / stretch_weight_x if shh: h = max(0, shh * (selfh - padding_y)) for key, value in c.pos_hint.items(): posy = value * (selfh - padding_y) if key == 'y': cy += padding_bottom + posy elif key == 'top': cy += padding_bottom + posy - h elif key == 'center_y': cy += padding_bottom - h / 2. + posy c.x = cx c.y = cy c.width = w c.height = h x += w + spacing if orientation == 'vertical': y = padding_bottom stretch_space = max(0.0, selfh - minimum_size_y) for c in self.children: shw = c.size_hint_x shh = c.size_hint_y w = c.width h = c.height cx = selfx + padding_left cy = selfy + y if shh: h = stretch_space * shh / stretch_weight_y if shw: w = max(0, shw * (selfw - padding_x)) for key, value in c.pos_hint.items(): posx = value * (selfw - padding_x) if key == 'x': cx += padding_left + posx elif key == 'right': cx += padding_left + posx - w elif key == 'center_x': cx += padding_left - w / 2. + posx c.x = cx c.y = cy c.width = w c.height = h y += h + spacing def add_widget(self, widget, index=0): widget.bind(pos_hint=self._trigger_layout) return super(BoxLayout, self).add_widget(widget, index) def remove_widget(self, widget): widget.unbind(pos_hint=self._trigger_layout) return super(BoxLayout, self).remove_widget(widget)
class StackLayout(Layout): '''Stack 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]. :data:`spacing` is a :class:`~kivy.properties.VariableListProperty`, default to [0, 0]. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between layout box and 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. :data:`padding` is a :class:`~kivy.properties.VariableListProperty`, default to [0, 0, 0, 0]. ''' 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. :data:`orientation` is an :class:`~kivy.properties.OptionProperty`, default to 'lr-tb'. Valid orientations are: 'lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt', 'bt-rl' .. versionchanged:: 1.5.0 :data:`orientation` now correctly handles all valid combinations of 'lr','rl','tb','bt'. Before this version only 'lr-tb' and 'tb-lr' were supported, and 'tb-lr' was misnamed and placed widgets from bottom to top and from right to left (reversed compared to what was expected). .. note:: lr mean Left to Right. rl mean Right to Left. tb mean Top to Bottom. bt mean Bottom to Top. ''' minimum_width = NumericProperty(0) '''Minimum width needed to contain all children. .. versionadded:: 1.0.8 :data:`minimum_width` is a :class:`kivy.properties.NumericProperty`, default to 0. ''' minimum_height = NumericProperty(0) '''Minimum height needed to contain all children. .. versionadded:: 1.0.8 :data:`minimum_height` is a :class:`kivy.properties.NumericProperty`, default to 0. ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''Minimum size needed to contain all children. .. versionadded:: 1.0.8 :data:`minimum_size` is a :class:`~kivy.properties.ReferenceListProperty` of (:data:`minimum_width`, :data:`minimum_height`) properties. ''' def __init__(self, **kwargs): super(StackLayout, self).__init__(**kwargs) self.bind( padding=self._trigger_layout, spacing=self._trigger_layout, children=self._trigger_layout, orientation=self._trigger_layout, size=self._trigger_layout, pos=self._trigger_layout) def do_layout(self, *largs): # optimize layout by preventing looking at the same attribute in a loop selfpos = self.pos selfsize = self.size orientation = self.orientation.split('-') padding_left = self.padding[0] padding_top = self.padding[1] padding_right = self.padding[2] padding_bottom = self.padding[3] padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom spacing_x, spacing_y = self.spacing lc = [] # Determine which direction and in what order to place the widgets posattr = [0] * 2 posdelta = [0] * 2 posstart = [0] * 2 for i in (0, 1): posattr[i] = 1 * (orientation[i] in ('tb', 'bt')) k = posattr[i] if orientation[i] == 'lr': # left to right posdelta[i] = 1 posstart[i] = selfpos[k] + padding_left elif orientation[i] == 'bt': # bottom to top posdelta[i] = 1 posstart[i] = selfpos[k] + padding_bottom elif orientation[i] == 'rl': # right to left posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_right else: # top to bottom posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_top innerattr, outerattr = posattr ustart, vstart = posstart deltau, deltav = posdelta del posattr, posdelta, posstart u = ustart # inner loop position variable v = vstart # outer loop position variable # space calculation, used for determining when a row or column is full if orientation[0] in ('lr', 'rl'): lu = self.size[innerattr] - padding_x sv = padding_y # size in v-direction, for minimum_size property su = padding_x # size in h-direction spacing_u = spacing_x spacing_v = spacing_y else: lu = self.size[innerattr] - padding_y sv = padding_x # size in v-direction, for minimum_size property su = padding_y # size in h-direction spacing_u = spacing_y spacing_v = spacing_x # space calculation, row height or column width, for arranging widgets lv = 0 urev = (deltau < 0) vrev = (deltav < 0) for c in reversed(self.children): # Issue#823: ReferenceListProperty doesn't allow changing # individual properties. # when the above issue is fixed we can remove csize from below and # access c.size[i] directly csize = c.size[:] # we need to update the whole tuple at once. if c.size_hint[0]: # calculate width csize[0] = c.size_hint[0] * (selfsize[0] - padding_x) if c.size_hint[1]: # calculate height csize[1] = c.size_hint[1] * (selfsize[1] - padding_y) c.size = tuple(csize) # does the widget fit in the row/column? if lu - c.size[innerattr] >= 0: lc.append(c) lu -= c.size[innerattr] + spacing_u lv = max(lv, c.size[outerattr]) continue # push the line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] p = [0, 0] # issue #823 p[innerattr] = u p[outerattr] = v if vrev: # v position is actually the top/right side of the widget # when going from high to low coordinate values, # we need to subtract the height/width from the position. p[outerattr] -= c2.size[outerattr] c2.pos = tuple(p) # issue #823 if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u v += deltav * lv v += deltav * spacing_v lc = [c] lv = c.size[outerattr] lu = selfsize[innerattr] - su - c.size[innerattr] - spacing_u u = ustart if lc: # push the last (incomplete) line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] p = [0, 0] # issue #823 p[innerattr] = u p[outerattr] = v if vrev: p[outerattr] -= c2.size[outerattr] c2.pos = tuple(p) # issue #823 if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u minsize = self.minimum_size[:] # issue #823 minsize[outerattr] = sv self.minimum_size = tuple(minsize)
class CircularLayout(Layout): '''Circular layout class. See module documentation for more information. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between the layout box and it's 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]. ''' start_angle = NumericProperty(0) '''Angle (in degrees) at which the first widget will be placed. Start counting angles from the X axis, going counterclockwise. :attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and defaults to 0 (start from the right). ''' circle_quota = BoundedNumericProperty(360, min=0, max=360) '''Size (in degrees) of the part of the circumference that will actually be used to place widgets. :attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty` and defaults to 360 (all the circumference). ''' direction = OptionProperty("ccw", options=("cw", "ccw")) '''Direction of widgets in the circle. :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise). ''' outer_radius_hint = NumericProperty(1) '''Sets the size of the outer circle. A number greater than 1 will make the widgets larger than the actual widget, a number smaller than 1 will leave a gap. :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and defaults to 1. ''' inner_radius_hint = NumericProperty(.6) '''Sets the size of the inner circle. A number greater than :attr:`outer_radius_hint` will cause glitches. The closest it is to :attr:`outer_radius_hint`, the smallest will be the widget in the layout. :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and defaults to 1. ''' radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint) '''Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint` in a list for convenience. See their documentation for more details. :attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`. ''' def _get_delta_radii(self): radius = min(self.width - self.padding[0] - self.padding[2], self.height - self.padding[1] - self.padding[3]) / 2. outer_r = radius * self.outer_radius_hint inner_r = radius * self.inner_radius_hint return outer_r - inner_r delta_radii = AliasProperty(_get_delta_radii, None, bind=("radius_hint", "padding", "size")) def __init__(self, **kwargs): super(CircularLayout, self).__init__(**kwargs) self.bind( start_angle=self._trigger_layout, parent=self._trigger_layout, # padding=self._trigger_layout, children=self._trigger_layout, size=self._trigger_layout, radius_hint=self._trigger_layout, pos=self._trigger_layout) def do_layout(self, *largs): # optimize layout by preventing looking at the same attribute in a loop len_children = len(self.children) if len_children == 0: return selfcx = self.center_x selfcy = self.center_y direction = self.direction cquota = radians(self.circle_quota) # selfw = self.width # selfh = self.height start_angle_r = radians(self.start_angle) padding_left = self.padding[0] padding_top = self.padding[1] padding_right = self.padding[2] padding_bottom = self.padding[3] padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom radius = min(self.width - padding_x, self.height - padding_y) / 2. outer_r = radius * self.outer_radius_hint inner_r = radius * self.inner_radius_hint middle_r = radius * sum(self.radius_hint) / 2. delta_r = outer_r - inner_r # calculate maximum space used by size_hint stretch_weight_angle = 0. for w in self.children: sha = w.size_hint_x if sha is None: raise ValueError( "size_hint_x cannot be None in a CircularLayout") else: stretch_weight_angle += sha sign = +1. angle_offset = start_angle_r if direction == 'cw': angle_offset = 2 * pi - start_angle_r sign = -1. for c in reversed(self.children): sha = c.size_hint_x shs = c.size_hint_y angle_quota = cquota / stretch_weight_angle * sha angle = angle_offset + (sign * angle_quota / 2) angle_offset += sign * angle_quota # kived: looking it up, yes. x = cos(angle) * radius + centerx; y = sin(angle) * radius + centery ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top c.center_x = ccx c.center_y = ccy if shs: s = delta_r * shs c.width = s c.height = s
class ListLayoutInt(Layout): spacing = VariableListProperty([0, 0], length=2) padding = VariableListProperty([0, 0, 0, 0], length=4) minimum_width = NumericProperty(0) minimum_height = NumericProperty(0) minimum_size = ReferenceListProperty(minimum_width, minimum_height) def update_minimum_width(self): minimum_width = 9999 for w in self.children: minimum_width = min(minimum_width, w.width) self.minimum_width = minimum_width def add_widget(self, widget, index=0): # update widget position relative to our own. widget.x = int(self.x + self.padding[0] + widget.x) if index != 0: widget.y = int(self.y + self.minimum_height) else: if len(self.children) > 0 and hasattr( self.children[0], 'stick_bottom') and self.children[0].stick_bottom: widget.y = int(self.children[0].y + self.children[0].height + self.spacing[1]) index = 1 else: widget.y = int(self.y) h = widget.height + self.spacing[1] for w in self.children: if hasattr(w, 'stick_bottom') and w.stick_bottom: continue w.y += h Logger.info('ListLayoutInt -- %s %s %s %s' % (self.x, self.y, widget.height, self.minimum_height)) # udate minimum width / height self.minimum_width = min(self.minimum_width, widget.width) if len(self.children) == 0: self.minimum_height = 2 * self.spacing[1] + widget.height else: self.minimum_height += self.spacing[1] + widget.height index = max(0, min(len(self.children), index)) # actuall insert into children list and canvas #self.children.insert(index, widgetBooleanProperty) super(ListLayoutInt, self).add_widget(widget, index) self.height = self.minimum_height self.width = self.minimum_width return True def remove_widget(self, widget): if widget not in self.children: return # find out from where we need to update the y location (i.e after the widget) #self.children.remove(widget) super(ListLayoutInt, self).remove_widget(widget) self.update_minimum_width() if len(self.children) == 0: self.minimum_height = 0 else: self.minimum_height -= self.spacing[1] + widget.height self.width = self.minimum_width self.height = self.minimum_height return True def do_layout(self, *largs): pass
class BoxLayout(Layout): '''Box layout class. See module documentation for more information. ''' spacing = NumericProperty(0) '''Spacing between children, in pixels. :attr:`spacing` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between layout box and 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]. ''' orientation = OptionProperty('horizontal', options=('horizontal', 'vertical')) '''Orientation of the layout. :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and defaults to 'horizontal'. Can be 'vertical' or 'horizontal'. ''' minimum_width = NumericProperty(0) '''Automatically computed minimum width needed to contain all children. .. versionadded:: 1.10.0 :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.10.0 :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.10.0 :attr:`minimum_size` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`minimum_width`, :attr:`minimum_height`) properties. It is read only. ''' def __init__(self, **kwargs): super(BoxLayout, self).__init__(**kwargs) update = self._trigger_layout fbind = self.fbind fbind('spacing', update) fbind('padding', update) fbind('children', update) fbind('orientation', update) fbind('parent', update) fbind('size', update) fbind('pos', update) def _iterate_layout(self, sizes): # optimize layout by preventing looking at the same attribute in a loop len_children = len(sizes) padding_left, padding_top, padding_right, padding_bottom = self.padding spacing = self.spacing orientation = self.orientation padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom # calculate maximum space used by size_hint stretch_sum = 0. has_bound = False hint = [None] * len_children # min size from all the None hint, and from those with sh_min minimum_size_bounded = 0 if orientation == 'horizontal': minimum_size_y = 0 minimum_size_none = padding_x + spacing * (len_children - 1) for i, ((w, h), (shw, shh), _, (shw_min, shh_min), (shw_max, _)) in enumerate(sizes): if shw is None: minimum_size_none += w else: hint[i] = shw if shw_min: has_bound = True minimum_size_bounded += shw_min elif shw_max is not None: has_bound = True stretch_sum += shw if shh is None: minimum_size_y = max(minimum_size_y, h) elif shh_min: minimum_size_y = max(minimum_size_y, shh_min) minimum_size_x = minimum_size_bounded + minimum_size_none minimum_size_y += padding_y else: minimum_size_x = 0 minimum_size_none = padding_y + spacing * (len_children - 1) for i, ((w, h), (shw, shh), _, (shw_min, shh_min), (_, shh_max)) in enumerate(sizes): if shh is None: minimum_size_none += h else: hint[i] = shh if shh_min: has_bound = True minimum_size_bounded += shh_min elif shh_max is not None: has_bound = True stretch_sum += shh if shw is None: minimum_size_x = max(minimum_size_x, w) elif shw_min: minimum_size_x = max(minimum_size_x, shw_min) minimum_size_y = minimum_size_bounded + minimum_size_none minimum_size_x += padding_x self.minimum_size = minimum_size_x, minimum_size_y # do not move the w/h get above, it's likely to change on above line selfx = self.x selfy = self.y if orientation == 'horizontal': stretch_space = max(0.0, self.width - minimum_size_none) dim = 0 else: stretch_space = max(0.0, self.height - minimum_size_none) dim = 1 if has_bound: # make sure the size_hint_min/max are not violated if stretch_space < 1e-9: # there's no space, so just set to min size or zero stretch_sum = stretch_space = 1. for i, val in enumerate(sizes): sh = val[1][dim] if sh is None: continue sh_min = val[3][dim] if sh_min is not None: hint[i] = sh_min else: hint[i] = 0. # everything else is zero else: # hint gets updated in place self.layout_hint_with_bounds(stretch_sum, stretch_space, minimum_size_bounded, (val[3][dim] for val in sizes), (elem[4][dim] for elem in sizes), hint) if orientation == 'horizontal': x = padding_left + selfx size_y = self.height - padding_y for i, (sh, ((w, h), (_, shh), pos_hint, _, _)) in enumerate(zip(reversed(hint), reversed(sizes))): cy = selfy + padding_bottom if sh: w = max(0., stretch_space * sh / stretch_sum) if shh: h = max(0, shh * size_y) for key, value in pos_hint.items(): posy = value * size_y if key == 'y': cy += posy elif key == 'top': cy += posy - h elif key == 'center_y': cy += posy - (h / 2.) yield len_children - i - 1, x, cy, w, h x += w + spacing else: y = padding_bottom + selfy size_x = self.width - padding_x for i, (sh, ((w, h), (shw, _), pos_hint, _, _)) in enumerate(zip(hint, sizes)): cx = selfx + padding_left if sh: h = max(0., stretch_space * sh / stretch_sum) if shw: w = max(0, shw * size_x) for key, value in pos_hint.items(): posx = value * size_x if key == 'x': cx += posx elif key == 'right': cx += posx - w elif key == 'center_x': cx += posx - (w / 2.) yield i, cx, y, w, h y += h + spacing def do_layout(self, *largs): children = self.children if not children: l, t, r, b = self.padding self.minimum_size = l + r, t + b return for i, x, y, w, h in self._iterate_layout([ (c.size, c.size_hint, c.pos_hint, c.size_hint_min, c.size_hint_max) for c in children ]): try: c = children[i] c.pos = x, y shw, shh = c.size_hint 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) except: pass def add_widget(self, widget, index=0, canvas=None): widget.fbind('pos_hint', self._trigger_layout) return super(BoxLayout, self).add_widget(widget, index, canvas) def remove_widget(self, widget): widget.funbind('pos_hint', self._trigger_layout) return super(BoxLayout, self).remove_widget(widget)
from kivy.clock import Clock from kivy.uix.screenmanager import ScreenManager, Screen from kivy.uix.settings import SettingsWithSidebar import pandas as pd from json_settings import settings_json import random, os, time, threading from kivy.config import Config Config.set('graphics', 'width', '800') Config.set('graphics', 'height', '480') Config.set('kivy', 'keyboard_mode', 'dock') Config.write() colour = VariableListProperty() colour = (1, 1, 1, 0) strain = StringProperty('None') name = StringProperty('None') unitGlob = 'None' #Camera seperate thread class class useCamera(threading.Thread): def __init__(self, filename): threading.Thread.__init__(self) self.filename = filename def run(self): try: