class PhotoEditor(Screen): def __init__(self, sm, **kwargs): super(PhotoEditor, self).__init__(**kwargs) # start speech recognition thread threading.Thread(target=speech.speech_main, args=(self, )).start() self.sm = sm # initialize image object self.picture = Picture(filepath) self.canvas.add(self.picture) # create slider group self.slider_group = AnimGroup() self.canvas.add(self.slider_group) # initialize slider self.slider = Slider(1.0, 0, 4) self.slider_line = Line(points=[ 0.98 * Window.width, 0.02 * Window.height, 0.98 * Window.width, 0.98 * Window.height ], width=10) self.slider_group.add(self.slider_line) dim_size = min(0.03 * Window.width, 0.03 * Window.height) self.canvas.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.slider_knob = CEllipse(cpos=(0.98 * Window.width, 0.02 * Window.height), csize=(dim_size, dim_size)) self.slider_group.add(self.slider_knob) # create overlay (4 rectangles within overlay class to mimic crop frame) self.overlay = Overlay(0, 0, 0, 0, self.picture.rectangle.size, self.picture.rectangle.pos) self.canvas.add(self.overlay) # current modification self.mode; possible options are: # - None: no self.mode selected # - 'brightness' : change brightness using slider # - 'contrast' : change contrast using slider # - 'zoom' : makes photo bigger / smaller # - 'crop' : change size of photo self.modes = [ 'save', 'redo', 'undo', 'sticker', 'pixelate', 'invert', 'grayscale', 'transparent', 'saturation', 'sharpness', 'brightness', 'contrast', 'rotate', 'zoom', 'crop' ] self.slider_modes = [ 'brightness', 'contrast', 'saturation', 'sharpness' ] self.mode = None self.mode_instructions = { # we only have instructions for modes that you enter, modes that are applied as soon as the mode's voice command is said are omitted 'crop' : '[font=./fonts/Cairo-Bold]Crop Instructions: [/font][font=./fonts/Cairo-Regular] Use one or two hands; Start with hand(s) outside of the image and move your hand\ntowards the screen until the cursor(s) turns green. Then, move your hands left/right/up/down in toward the picture\nuntil you are happy with the crop. Finally, pull your hand(s) away from the screen until the cursor(s) turn yellow.[/font]', 'zoom' : '[font=./fonts/Cairo-Bold]Zoom Instructions: [/font][font=./fonts/Cairo-Regular] Move two hands towards the screen until both cursors turns green, then move\nthem horizontally/vertically towards or away from each other to zoom out or zoom in. When you are satified,\npull both hands away from the screen until the cursors turn yellow.\n\nTo zoom using your voice, say "in" to zoom in, and "out" to zoom out.[/font]', 'rotate' : '[font=./fonts/Cairo-Bold]Rotate Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then rotate your hand as if you were\nturning a knob so that your fingers face left/right. The picture will rotate 90º in the direction of your fingers.\n\nTo rotate the picture using your voice, say "rotate" and the picture will rotate 90º counterclockwise.[/font]', 'contrast' : '[font=./fonts/Cairo-Bold]Contrast Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the contrast. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the contrast using your voice, say "increase" (or a synonym) to increase the contrast, and "decrease" (or a\nsynonym) to decrease the contrast.[/font]', 'brightness' : '[font=./fonts/Cairo-Bold]Brightness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the brightness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the brightness using your voice, say "increase" (or a synonym) to increase the brightness, and "decrease" (or a\nsynonym)to decrease the brightness.[/font]', 'sharpness' : '[font=./fonts/Cairo-Bold]Sharpness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the sharpness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the sharpness using your voice, say "increase" (or a synonym) to increase the sharpness, and "decrease" (or a\nsynonym)to decrease the sharpness.[/font]', 'saturation' : '[font=./fonts/Cairo-Bold]Saturation Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the saturation. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the saturation using your voice, say "increase" (or a synonym) to increase the saturation, and "decrease" (or a\nsynonym)to decrease the saturation.[/font]', 'pixelate' : '[font=./fonts/Cairo-Bold]Pixelate Instructions: [/font][font=./fonts/Cairo-Regular] To increase the pixelation of the image by voice, say "increase" (or a synonym).\nTo return the picture to its original state, say "undo." [/font]', 'sticker' : '[font=./fonts/Cairo-Bold]Sticker Instructions: [/font][font=./fonts/Cairo-Regular] View the names of the stickers on the bottom of the screen by hovering over them. Then, hover\nover the part of the image where you want to place the sticker and say the name of the sticker you want to apply.[/font]', 'transparent' : '[font=./fonts/Cairo-Bold]Transparent Instructions: [/font][font=./fonts/Cairo-Regular] Hover over the area you wish to make transparent and say "apply". You can also modify\nthe transparency threshold in settings. To get to the settings page, hover over the gear in the top right corner.[/font]', } self.mode_instructions_label = Label(halign='center', markup=True) self.add_widget(self.mode_instructions_label) # add icon bar on left_hand side self.icon_label = Label() self.icon_bar = IconBar(self.modes, self.icon_label) self.canvas.add(self.icon_bar) self.add_widget(self.icon_label) # sticker bar self.sticker_label = Label() self.sticker_bar = StickerBar(self.sticker_label) self.add_widget(self.sticker_label) # settings button self.settings_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.97 * Window.width - Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/settings.png').texture) self.canvas.add(self.settings_button) # menu button self.menu_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.96 * Window.width - 2 * Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/menu.png').texture) self.canvas.add(self.menu_button) self.switch_to_timer = None # Hand objects that represent and show palm positions self.hands = [Hand(1), Hand(2)] for h in self.hands: self.canvas.add(h) def on_enter(self): print(self.sm.recent_screen) if self.sm.recent_screen == 'home': if self.picture in self.canvas.children: self.canvas.remove(self.picture) self.picture = Picture(filepath) self.canvas.add(Color(1, 1, 1, 1)) self.canvas.add(self.picture) self.canvas.remove(self.overlay) self.canvas.add(self.overlay) self.settings_button.on_update() self.menu_button.on_update() # reset icon bar self.canvas.remove(self.icon_bar) self.icon_bar = IconBar(self.modes, self.icon_label) self.canvas.add(self.icon_bar) # reset sticker bar if self.sticker_bar in self.canvas.children: self.canvas.remove(self.sticker_bar) self.sticker_bar = StickerBar(self.sticker_label) self.canvas.add(self.sticker_bar) else: self.sticker_bar = StickerBar(self.sticker_label) for h in self.hands: self.canvas.remove(h) self.canvas.add(h) self.on_layout((Window.width, Window.height)) def on_update(self): if DARK_MODE: self.sticker_label.color = (1, 1, 1, 1) self.icon_label.color = (1, 1, 1, 1) self.mode_instructions_label.color = (1, 1, 1, 1) else: self.sticker_label.color = (0, 0, 0, 1) self.icon_label.color = (0, 0, 0, 1) self.mode_instructions_label.color = (0, 0, 0, 1) # update instructions for the mode if applicable if ENABLE_INSTRUCTIONS and self.mode in self.mode_instructions.keys(): self.mode_instructions_label.text = self.mode_instructions[ self.mode] else: self.mode_instructions_label.text = '' screen_hands = list(filter(lambda h: not h.id == -1, self.hands)) # icon menu bar hover for hand in screen_hands: hovered_icon = self.icon_bar.identify_icon( hand.pos[0] * Window.width, hand.pos[1] * Window.height) if not hovered_icon == None: self.icon_bar.show_label(hovered_icon) else: self.icon_label.text = '' # sticker menu bar hover if self.sticker_bar in self.canvas.children: for hand in screen_hands: hovered_sticker = self.sticker_bar.identify_sticker( hand.pos[0] * Window.width, hand.pos[1] * Window.height) if not hovered_sticker == None: self.sticker_bar.show_label(hovered_sticker) else: self.sticker_label.text = '' active_hands = list(filter(lambda h: h.active, self.hands)) # update icon bar to show current mode if not self.mode == None: self.icon_bar.change_mode(self.modes.index(self.mode)) ###################################### ## SLIDER ## ###################################### self.update_slider(active_hands=active_hands) ###################################### ## SETTINGS/MENU BUTTON ## ###################################### if screen_hands: norm_pt = scale_point(screen_hands[0].leap_hand.palm_pos, kLeapRange) screen_xy = screen_hands[0].hand.to_screen_xy(norm_pt) self.settings_button.set_screen_pos(screen_xy, norm_pt[2]) self.menu_button.set_screen_pos(screen_xy, norm_pt[2]) if self.settings_button.is_on and self.switch_to_timer == None: self.switch_to_timer = time.time() self.switch_to('settings') if self.menu_button.is_on and self.switch_to_timer == None: self.switch_to_timer = time.time() self.switch_to('home') ###################################### ## CROP ## ###################################### # one handed if self.mode == 'crop' and len(active_hands) == 1: # get crop zone (top, right, left, bottom) image_pos = self.picture.rectangle.pos image_size = self.picture.rectangle.size hand_pos = active_hands[0].pos # get magnitude of crop crop_amount = active_hands[0].pos - active_hands[0].recent_pos # if hand is directly to the left of the image: if hand_pos[0] * Window.width < image_pos[0] and image_pos[ 1] < hand_pos[1] * Window.height < image_pos[ 1] + image_size[1]: self.overlay.change_left_delta(crop_amount[0] * Window.width) # if hand is directly to the right of the image: if image_pos[0] + image_size[0] < hand_pos[ 0] * Window.width and image_pos[1] < hand_pos[ 1] * Window.height < image_pos[1] + image_size[1]: self.overlay.change_right_delta(-crop_amount[0] * Window.width) # if hand is directly below the image: if hand_pos[1] * Window.height < image_pos[1] and image_pos[ 0] < hand_pos[0] * Window.width < image_pos[ 0] + image_size[0]: self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # if hand is directly above the image: if image_pos[1] + image_size[1] < hand_pos[ 1] * Window.height and image_pos[0] < hand_pos[ 0] * Window.width < image_pos[0] + image_size[0]: self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in top left if hand_pos[0] * Window.width < image_pos[0] and image_pos[ 1] + image_size[1] < hand_pos[1] * Window.height: self.overlay.change_left_delta(crop_amount[0] * Window.width) self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in top right if image_pos[0] + image_size[ 0] < hand_pos[0] * Window.width and image_pos[ 1] + image_size[1] < hand_pos[1] * Window.height: self.overlay.change_right_delta(-crop_amount[0] * Window.width) self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in bottom left if hand_pos[0] * Window.width < image_pos[ 0] and hand_pos[1] * Window.height < image_pos[1]: self.overlay.change_left_delta(crop_amount[0] * Window.width) self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # if hand is in bottom right if image_pos[0] + image_size[ 0] < hand_pos[0] * Window.width and hand_pos[ 1] * Window.height < image_pos[1]: self.overlay.change_right_delta(-crop_amount[0] * Window.width) self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # two handed if self.mode == 'crop' and len(active_hands) == 2: # get crop zone (top, right, left, bottom) image_pos = self.picture.rectangle.pos image_size = self.picture.rectangle.size hand_pos_A = active_hands[0].pos hand_pos_B = active_hands[1].pos # get magnitude of crop crop_amount_A = active_hands[0].pos - active_hands[0].recent_pos crop_amount_B = active_hands[1].pos - active_hands[1].recent_pos # hand A in top left if hand_pos_A[0] < 0.5 and hand_pos_A[1] > 0.5: self.overlay.change_left_delta(crop_amount_A[0] * Window.width) self.overlay.change_top_delta(-crop_amount_A[1] * Window.height) # hand B in top left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] > 0.5: self.overlay.change_left_delta(crop_amount_B[0] * Window.width) self.overlay.change_top_delta(-crop_amount_B[1] * Window.height) # hand A in top right if hand_pos_A[0] > 0.5 and hand_pos_A[1] > 0.5: self.overlay.change_right_delta(-crop_amount_A[0] * Window.width) self.overlay.change_top_delta(-crop_amount_A[1] * Window.height) # hand B in top right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] > 0.5: self.overlay.change_right_delta(-crop_amount_B[0] * Window.width) self.overlay.change_top_delta(-crop_amount_B[1] * Window.height) # hand A in bottom left if hand_pos_A[0] < 0.5 and hand_pos_A[1] < 0.5: self.overlay.change_left_delta(crop_amount_A[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_A[1] * Window.height) # hand B in bottom left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] < 0.5: self.overlay.change_left_delta(crop_amount_B[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_B[1] * Window.height) # hand A in bottom right if hand_pos_A[0] > 0.5 and hand_pos_A[1] < 0.5: self.overlay.change_right_delta(-crop_amount_A[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_A[1] * Window.height) # hand B in bottom right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] < 0.5: self.overlay.change_right_delta(-crop_amount_B[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_B[1] * Window.height) ###################################### ## ROTATE ## ###################################### if self.mode == 'rotate' and len(active_hands) == 1: if all([ state == None for state in active_hands[0].recent_turn_states ]) and active_hands[0].turn_state == 'right': self.picture.change_rotation(angle=90, update_dims=True) self.picture.on_update() self.picture.update() elif all([ state == None for state in active_hands[0].recent_turn_states ]) and active_hands[0].turn_state == 'left': self.picture.change_rotation(angle=-90, update_dims=True) self.picture.on_update() self.picture.update() ###################################### ## ZOOM ## ###################################### self.update_on_zoom(active_hands=active_hands) ###################################### ## HAND OBJECTS ## ###################################### # update each hand for h in self.hands: h.on_update() # eliminate rapid screen switching bug if self.switch_to_timer and time.time() - self.switch_to_timer > 2: self.switch_to_timer = None def update_slider(self, active_hands=[], keyword=None): # update slider value if in slider self.mode and 1 hand is engaged (i.e. z < 0.6) if (self.mode in self.slider_modes) and \ (len(active_hands) == 1 or keyword in commands.slider_commands): # add green slider knob self.slider_group.remove_all() self.slider_group.add(self.slider_line) self.slider_group.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.slider_group.add(self.slider_knob) if len(active_hands) == 1: delta = active_hands[0].pos - active_hands[0].recent_pos delta_y = delta[1] elif keyword in commands.slider_commands: if keyword == 'up': delta_y = speech_deltas['slider'] elif keyword == 'down': delta_y = -speech_deltas['slider'] percent = self.slider.change_value_delta(delta_y) self.slider_knob.cpos = (self.slider_knob.cpos[0], Window.height * (0.96 * percent + 0.02)) # update brightness or contrast based on slider value if self.mode == 'pixelate': x = self.slider.value new_min = 0 new_max = 1 old_min = 1 old_max = 4 normalized_x = ((new_max - new_min) / (old_max - old_min)) * (x - old_max) + new_max print(x) print(normalized_x) # self.picture.pixelate(self.slider.value) self.picture.pixelate(normalized_x) # if self.mode == 'rotate': # self.picture.change_rotation(angle=90*(self.slider.value-1), update_dims=False) elif self.mode in self.slider_modes: # works for change_contrast, change_brightness, change_sharpness, change_saturation eval('self.picture.change_' + self.mode + '(' + str(self.slider.value) + ')') self.picture.on_update() elif (self.mode in self.slider_modes): # add yellow slider knob self.slider_group.remove_all() self.slider_group.add(self.slider_line) self.slider_group.add(Color(rgb=(.94, .76, 0))) self.slider_group.add(self.slider_knob) else: self.slider_group.remove_all() def update_on_zoom(self, active_hands=[], keyword=None): if self.mode == 'zoom': # two handed if len(active_hands) == 2: hand_pos_A = active_hands[0].pos hand_pos_B = active_hands[1].pos # get magnitude of zoom zoom_amount_A = active_hands[0].pos - active_hands[0].recent_pos zoom_amount_B = active_hands[1].pos - active_hands[1].recent_pos delta_w = 0 delta_h = 0 # hand A in top left if hand_pos_A[0] < 0.5 and hand_pos_A[1] > 0.5: delta_w += -zoom_amount_A[0] * Window.width delta_h += zoom_amount_A[1] * Window.height # hand B in top left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] > 0.5: delta_w += -zoom_amount_B[0] * Window.width delta_h += zoom_amount_B[1] * Window.height # hand A in top right if hand_pos_A[0] > 0.5 and hand_pos_A[1] > 0.5: delta_w += zoom_amount_A[0] * Window.width delta_h += zoom_amount_A[1] * Window.height # hand B in top right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] > 0.5: delta_w += zoom_amount_B[0] * Window.width delta_h += zoom_amount_B[1] * Window.height # hand A in bottom left if hand_pos_A[0] < 0.5 and hand_pos_A[1] < 0.5: delta_w += -zoom_amount_A[0] * Window.width delta_h += -zoom_amount_A[1] * Window.height # hand B in bottom left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] < 0.5: delta_w += -zoom_amount_B[0] * Window.width delta_h += -zoom_amount_B[1] * Window.height # hand A in bottom right if hand_pos_A[0] > 0.5 and hand_pos_A[1] < 0.5: delta_w += zoom_amount_A[0] * Window.width delta_h += -zoom_amount_A[1] * Window.height # hand B in bottom right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] < 0.5: delta_w += zoom_amount_B[0] * Window.width delta_h += -zoom_amount_B[1] * Window.height self.picture.zoom_delta(delta_w=delta_w, delta_h=delta_h) elif keyword: if keyword == 'in': delta_w = speech_deltas['zoom'] delta_h = speech_deltas['zoom'] elif keyword == 'out': delta_w = -speech_deltas['zoom'] delta_h = -speech_deltas['zoom'] delta_w *= Window.width delta_h *= Window.height self.picture.zoom_delta(delta_w=delta_w, delta_h=delta_h) def on_layout(self, win_size): for h in self.hands: h.on_layout(win_size) self.picture.on_layout(win_size) self.overlay.on_layout(self.picture.rectangle.size, self.picture.rectangle.pos) # update slider positioning percent = self.slider.get_slider_percent() self.slider_knob.cpos = (0.98 * Window.width, Window.height * (0.96 * percent + 0.02)) dim_size = min(0.03 * Window.width, 0.03 * Window.height) self.slider_knob.csize = (dim_size, dim_size) self.slider_line.points = [ 0.98 * Window.width, 0.02 * Window.height, 0.98 * Window.width, 0.98 * Window.height ] # update side mode icon bar self.icon_bar.on_layout() # update sticker bar self.sticker_bar.on_layout() # update settings and menu buttons self.settings_button.update_pos_and_size( pos=(0.97 * Window.width - Window.width / 15, 0.98 * Window.height - Window.width / 15), size=(Window.width / 15, Window.width / 15)) self.menu_button.update_pos_and_size( pos=(0.96 * Window.width - 2 * Window.width / 15, 0.98 * Window.height - Window.width / 15), size=(Window.width / 15, Window.width / 15)) # update instructions label self.mode_instructions_label.center_x = Window.width / 2 self.mode_instructions_label.center_y = 19 * Window.height / 20 self.mode_instructions_label.font_size = str( Window.width // 170) + 'sp' # maps key presses to changing editing self.mode # to be replaced by speech commands in V2 @mainthread def on_speech_recognized(self, keyword): print("keyword: ", keyword) old_mode = self.mode # change mode if keyword relates to a mode if keyword in self.modes: self.mode = keyword if keyword in commands.terminator: sys.exit() # TODO: if you call undo just once it sometimes doesn't work the first time if self.mode == 'undo': self.picture.undo() self.picture.on_update() self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) elif self.mode == 'redo': self.picture.redo() self.picture.on_update() self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) elif self.mode in self.slider_modes and keyword in commands.slider_commands: self.update_slider(keyword=keyword) elif self.mode == 'zoom' and keyword in commands.zoom_commands: self.update_on_zoom(keyword=keyword) # if switching from one self.mode to a different self.mode, update picture history elif not old_mode == self.mode: if old_mode == 'zoom': self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) # update slider to default value to start fresh next time if old_mode in self.slider_modes: percent = self.slider.change_value(1.0) self.slider_knob.cpos = (self.slider_knob.cpos[0], Window.height * (0.96 * percent + 0.02)) # commit crop and remove crop overlay if old_mode == 'crop': # actually crop self.picture.temp self.picture.crop(l=self.overlay.left_width, t=self.overlay.top_height, r=self.overlay.right_width, b=self.overlay.bottom_height) # reset crop overlay width and height params to 0,0,0,0 self.overlay.reset() self.picture.on_update() # ensure cropping change appears self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) # update image history with changes self.picture.update( ) # TODO: (fix) currently this updates history every time a self.mode is changed, even if the photo is not modified while in that self.mode # if current mode is sticker, add a sticker based on its name if self.mode == 'sticker': if self.sticker_bar not in self.canvas.children: self.canvas.add(self.sticker_bar) if keyword in self.sticker_bar.sticker_names: screen_hands = list( filter(lambda h: not h.id == -1, self.hands)) # if hand on screen and within picture area if len( screen_hands ) == 1 and self.picture.rectangle.pos[0] < screen_hands[0].pos[ 0] * Window.width < self.picture.rectangle.pos[ 0] + self.picture.rectangle.size[ 0] and self.picture.rectangle.pos[ 1] < screen_hands[0].pos[ 1] * Window.height < self.picture.rectangle.pos[ 1] + self.picture.rectangle.size[1]: # get picture position (ignoring screen position of the image) x = screen_hands[0].pos[ 0] * Window.width - self.picture.rectangle.pos[0] y = self.picture.rectangle.size[1] - ( screen_hands[0].pos[1] * Window.height - self.picture.rectangle.pos[1]) self.picture.add_sticker('./stickers/' + keyword + '.png', x, y) self.picture.on_update() self.picture.update() else: self.canvas.remove(self.sticker_bar) if self.mode == 'transparent': if keyword == "apply": screen_hands = list( filter(lambda h: not h.id == -1, self.hands)) if len( screen_hands ) == 1 and self.picture.rectangle.pos[0] < screen_hands[0].pos[ 0] * Window.width < self.picture.rectangle.pos[ 0] + self.picture.rectangle.size[ 0] and self.picture.rectangle.pos[ 1] < screen_hands[0].pos[ 1] * Window.height < self.picture.rectangle.pos[ 1] + self.picture.rectangle.size[1]: x = screen_hands[0].pos[ 0] * Window.width - self.picture.rectangle.pos[0] y = screen_hands[0].pos[ 1] * Window.height - self.picture.rectangle.pos[1] mask = self.picture.magic_wand(x, y, transparency_threshold) self.picture.make_transparent(mask) self.picture.on_update() self.picture.update() if self.mode == 'rotate': if old_mode == "rotate": self.picture.change_rotation(angle=90, update_dims=True) self.picture.on_update() self.picture.update() elif self.mode == 'invert': self.picture.invert() self.picture.on_update() self.picture.update() elif self.mode == 'grayscale': self.picture.grayscale() self.picture.on_update() self.picture.update() # if current mode is save, save the image if self.mode == 'save': self.picture.save_image()
class LevelSelectScreen(Screen): def __init__(self, **kwargs): super(LevelSelectScreen, self).__init__(**kwargs) Window.clearcolor = colors['grey'].rgba self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.label = Label(text='Play it by Gear', font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) self.buttons = [] self.id_counter = 0 self.audio = Audio(2) self.mixer = Mixer() self.audio.set_generator(self.mixer) self.start_time = None self.switch_level = None self.border_color = colors['dark_grey'] self.border = Line(rectangle=(0, 0, Window.width, Window.height), width=Window.width * 0.03) self.anim_group.add(self.border_color) self.anim_group.add(self.border) self.on_layout((Window.width, Window.height)) def generate_buttons(self, row_pos, num_buttons, max_num_buttons, num_rows, margin): for i in range(num_buttons): self.id_counter += 1 square_dim = min(Window.width / (max_num_buttons + 2), Window.height / (num_rows + 1)) button = LevelButton(pos=(i / 6 * (Window.width) + margin, row_pos), size=(square_dim, square_dim), id=self.id_counter) self.buttons.append(button) self.anim_group.add(button) def on_touch_up(self, touch): for b in self.buttons: if b.is_clicked(touch): self.mixer.add( WaveGenerator(WaveFile("./data/button_press.wav"))) self.start_time = time.time() edit_progress_file(b.id, 'y') self.switch_level = 'Level ' + str(b.id) def on_enter(self): # update button colors by regenerating buttons self.on_layout((Window.width, Window.height)) def on_layout(self, winsize): # update level buttons self.anim_group.remove_all() self.id_counter = 0 self.buttons = [] self.generate_buttons(row_pos=(3 * Window.height / 5 - Window.height / 10), num_buttons=4, max_num_buttons=5, num_rows=3, margin=Window.width / 6) self.generate_buttons(row_pos=(2 * Window.height / 5 - Window.height / 10), num_buttons=5, max_num_buttons=5, num_rows=3, margin=Window.width / 12) self.generate_buttons(row_pos=(1 * Window.height / 5 - Window.height / 10), num_buttons=4, max_num_buttons=5, num_rows=3, margin=Window.width / 6) # update title label self.label.center_x = Window.width / 2 self.label.center_y = 5 * Window.height / 6 self.label.font_size = str(Window.width // 15) + 'sp' # update border self.border_color = colors['dark_grey'] self.border = Line(rectangle=(0, 0, Window.width, Window.height), width=Window.width * 0.03) self.anim_group.add(self.border_color) self.anim_group.add(self.border) def on_update(self): self.audio.on_update() if self.start_time and time.time() - self.start_time > 0.13: self.start_time = None self.switch_to(self.switch_level)
class IconBar(InstructionGroup): def __init__(self, modes, label): super(IconBar, self).__init__() self.modes = modes self.label = label self.icons = [] self.icon_group = AnimGroup() self.add(self.icon_group) self.generate_icons() def generate_icons(self): if photo_editor.DARK_MODE: self.icon_group.add(Color(rgb=(1, 1, 1))) else: self.icon_group.add(Color(rgb=(0, 0, 0))) for i in range(len(self.modes)): icon = Rectangle( pos=(0.02 * Window.width, Window.height * (0.1 + 0.8 * i / len(self.modes))), size=(0.8 * Window.height / len(self.modes), 0.8 * Window.height / len(self.modes)), texture=CoreImage('icons/' + self.modes[i] + '.png').texture) self.icons.append(icon) self.icon_group.add(icon) def change_mode(self, mode_index): self.icon_group.remove_all() for i in range(len(self.icons)): if photo_editor.DARK_MODE: self.icon_group.add(Color(rgb=(1, 1, 1))) else: self.icon_group.add(Color(rgb=(0, 0, 0))) if i == mode_index: # change color before adding self.icon_group.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.icon_group.add(self.icons[i]) def on_layout(self): for i in range(len(self.modes)): self.icons[i].pos = (0.02 * Window.width, Window.height * (0.1 + 0.8 * i / len(self.modes))) self.icons[i].size = (0.8 * Window.height / len(self.modes), 0.8 * Window.height / len(self.modes)) # returns mode of icon at screen position (x, y) def identify_icon(self, x, y): # if x-position is reasonable if 0.02 * Window.width < x < 0.02 * Window.width + 0.5 * Window.height / len( self.modes): # check for icon y-position match for i in range(len(self.modes)): if self.icons[i].pos[ 1] < y < self.icons[i].pos[1] + self.icons[i].size[1]: return self.modes[i] return None def show_label(self, icon): i = self.modes.index(icon) self.label.text = icon self.label.center_x = self.icons[i].pos[0] + self.icons[i].size[ 0] + self.label.texture_size[0] * 0.6 self.label.center_y = self.icons[i].pos[1] + self.icons[i].size[1] / 2
class LevelEasyMediumScreen(Screen): def __init__(self, level, notes, goal_values, gear_values, **kwargs): super(LevelEasyMediumScreen, self).__init__(**kwargs) # set up notes for the level self.notes = notes # set up gear values for the levels self.goal_values = goal_values # set up gear values for the levels self.gear_values = gear_values self.level = level # only turn on tutorial for level 1 if self.level == 1: self.use_tutorial = True self.tutorial_screen = 'A' self.goal_play_status = None self.gear_play_status = None self.size_dim = min(Window.width / 6, Window.height / 6) self.tutorial_full_overlay = CRectangle(cpos=(Window.width / 2, Window.height / 2), csize=(Window.width, Window.height)) self.tutorial_options_overlay = Rectangle( pos=(0, 0), size=(Window.width, self.size_dim + Window.height / 25)) self.tutorial_musicbox_overlay = Rectangle( pos=(Window.width // 2, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.tutorial_gearbox_overlay = Rectangle( pos=(0, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.skip_image = CoreImage('images/skip_tutorial.png') self.tutorial_skip_button = Rectangle( pos=(0.98 * Window.width - (self.skip_image.width * self.size_dim / 300), 0.98 * Window.height - self.skip_image.height * self.size_dim / 300), size=(self.skip_image.width * self.size_dim / 300, self.skip_image.height * self.size_dim / 300), texture=self.skip_image.texture) else: self.use_tutorial = False ############################################ ### GOAL MUSIC ### ############################################ self.goal_audio = Audio(2) self.goal_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.goal_tempo_map = SimpleTempoMap(120) self.goal_sched = AudioScheduler(self.goal_tempo_map) # connect scheduler into audio system self.goal_audio.set_generator(self.goal_sched) self.goal_sched.set_generator(self.goal_synth) # generate goal music self.goal_music = MusicPlayer(notes=self.notes, sched=self.goal_sched, synth=self.goal_synth, channel=1, tempo_map=self.goal_tempo_map) self.goal_music.update_tempo(self.goal_values[0]) self.goal_music.update_instrument(self.goal_values[1]) self.goal_music.update_pitch(self.goal_values[2]) self.goal_music.update_volume(self.goal_values[3]) self.goal_music_seq = self.goal_music.generate() ############################################ ### GEAR MUSIC ### ############################################ self.gear_audio = Audio(2) self.gear_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.gear_tempo_map = SimpleTempoMap(120) self.gear_sched = AudioScheduler(self.gear_tempo_map) # connect scheduler into audio system self.gear_audio.set_generator(self.gear_sched) self.gear_sched.set_generator(self.gear_synth) # generate gear music self.gear_music = MusicPlayer(notes=self.notes, sched=self.gear_sched, synth=self.gear_synth, channel=1, tempo_map=self.gear_tempo_map) self.gear_music_seq = None ############################################ ### BACKGROUND UI COMPONENTS ### ############################################ self.gear_area = GearArea() self.canvas.add(self.gear_area) self.music_box_area = MusicBoxArea() self.canvas.add(self.music_box_area) self.options = LevelOptions( level=level, goal_music_seq=self.goal_music_seq, duration=self.goal_music.duration, edit_goal_play_status=self.edit_goal_play_status) self.canvas.add(self.options) self.label = Label(text=kwargs['name'], font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) ########################################### ### GEAR LABELS ### ########################################### self.tempo_label = Label(text='Tempo (bpm)', font_name='./fonts/PassionOne-Regular', color=(0.7254901960784313, 0.5529411764705883, 0.8196078431372549, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (0.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.instrument_label = Label( text='Instrument', font_name='./fonts/PassionOne-Regular', color=(0.996078431372549, 0.8431372549019608, 0.4, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (1.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.pitch_label = Label(text='Pitch (semitones)', font_name='./fonts/PassionOne-Regular', color=(1.0, 0.6509803921568628, 0.09019607843137255, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (2.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.volume_label = Label( text='Volume', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (3.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.add_widget(self.volume_label) self.add_widget(self.pitch_label) self.add_widget(self.instrument_label) self.add_widget(self.tempo_label) ########################################### ### GEAR CONSTRUCTION ### ########################################### self.gears = [] self.gear_centers = [] self.gears_group = AnimGroup() self.canvas.add(self.gears_group) ########################################### ### GEARS ### ########################################### self.gear_storage_locations = [] self.gear_music_locations = [] self.gear_labels = [] self.set_up_gears() ########################################### ### PARTICLE EFFECT ### ########################################### self.win_ps = ParticleSystem('particles/star_particle.pex') self.win_ps.emitter_x = Window.width / 2 self.win_ps.emitter_y = Window.height / 2 self.add_widget(self.win_ps) self.you_win_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.5843137254901961, 0.796078431372549, 0.37254901960784315, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_win_label) self.lose_ps = ParticleSystem('particles/lose_particle.pex') self.lose_ps.emitter_x = Window.width / 2 self.lose_ps.emitter_y = Window.height / 2 self.add_widget(self.lose_ps) self.you_lose_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_lose_label) ########################################### ### ERROR MESSAGE ### ########################################### self.error_msg = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 20) + 'sp') self.add_widget(self.error_msg) # ########################################### # ### ADD TUTORIAL OVERLAYS ### # ########################################### if self.use_tutorial: self.canvas.add(Color(rgba=(0, 0, 0, 0.85))) self.canvas.add(self.tutorial_full_overlay) self.tutorial_label = Label( markup=True, text= "[font=./fonts/lato-bold]Welcome to the\n[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/PassionOne-Regular]Play It By Gear Tutorial[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/lato-bold]\n\nThe goal of this game is to make the \n goal song match the song you create by \nplacing the correct gears in a music box \n\n[/font] [font=./fonts/lato-light] (click to see the next \nstep of the tutorial)[/font]", color=(86 / 255, 189 / 255, 205 / 255, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 40) + 'sp', halign='center') self.add_widget(self.tutorial_label) self.canvas.add(self.tutorial_skip_button) self.on_layout((Window.width, Window.height)) def clear_overlays(self): if self.tutorial_full_overlay in self.canvas.children: self.canvas.remove(self.tutorial_full_overlay) if self.tutorial_options_overlay in self.canvas.children: self.canvas.remove(self.tutorial_options_overlay) if self.tutorial_gearbox_overlay in self.canvas.children: self.canvas.remove(self.tutorial_gearbox_overlay) if self.tutorial_musicbox_overlay in self.canvas.children: self.canvas.remove(self.tutorial_musicbox_overlay) while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def activate_overlays(self, overlay_names): self.clear_overlays() self.canvas.add(Color(rgba=(0, 0, 0, 0.85))) if 'full' in overlay_names: self.canvas.add(self.tutorial_full_overlay) if 'options' in overlay_names: self.canvas.add(self.tutorial_options_overlay) if 'gearbox' in overlay_names: self.canvas.add(self.tutorial_gearbox_overlay) if 'musicbox' in overlay_names: self.canvas.add(self.tutorial_musicbox_overlay) def get_scaled_x_y(self, winsize, x, y): width, height = winsize # scaled_x = width/8 * (x+1) scaled_x = width / (len(self.gear_values) // 4 * 2) * (x + 0.5) scaled_y = height / 5.25 * (y + 0.5) + self.gear_area.position[1] return scaled_x, scaled_y def set_up_gears(self): self.gears = [] self.gears_group.remove_all() center_gear_location = (Window.width / 6 * 4.5, Window.height / 4 * 2.5) center_gear_size = min(Window.width / 10, Window.height / 10) self.center_gear = Gear(None, None, center_gear_size, 10, 'center', 0, center_gear_location, center_gear_location, 1, colors['dark_grey']) self.canvas.add(self.center_gear.color) self.gears_group.add(self.center_gear) self.center_gear_center = GearCenter(None, None, center_gear_location, center_gear_location, 'center', center_gear_size / 2, colors['dark_grey']) self.canvas.add(colors['dark_grey']) self.canvas.add(self.center_gear_center) self.play_center_gear = False self.music_gears = [] tempo_location = (center_gear_location[0], center_gear_location[1] + center_gear_size + center_gear_size / 5) instrument_location = (center_gear_location[0], center_gear_location[1] - center_gear_size - center_gear_size / 5) pitch_location = (center_gear_location[0] + center_gear_size + center_gear_size / 5, center_gear_location[1]) volume_location = (center_gear_location[0] - center_gear_size - center_gear_size / 5, center_gear_location[1]) self.music_box_gear_locations = [ tempo_location, instrument_location, pitch_location, volume_location ] counter = 0 label_font_size = min(Window.width // 80, Window.height // 80) for y in range(0, 4): for x in range(len(self.gear_values) // 4): gear_type = gear_type_map[y] size = min(Window.width / 10, Window.height / 10) music_pos = self.music_box_gear_locations[y] scaled_x, scaled_y = self.get_scaled_x_y( (Window.width, Window.height), x, y) gear = Gear(x, y, size, 8, gear_type, self.gear_values[counter], (scaled_x, scaled_y), music_pos, 0, colors['dark_grey']) self.gears.append(gear) self.canvas.add(gear.color) self.gears_group.add(gear) gear_center = GearCenter(x, y, (scaled_x, scaled_y), music_pos, gear_type, size / 2, colors['dark_grey']) self.gear_centers.append(gear_center) self.canvas.add(gear_center) ## white dots for storage purposes gear_loc = GearLocation((scaled_x, scaled_y), size / 2, x, y, gear_type) self.gear_storage_locations.append(gear_loc) self.canvas.add(gear_loc) text = str(self.gear_values[counter]) font_name = './fonts/PassionOne-Regular' if y == 3: # get volume as percent text = str(100 * self.gear_values[counter] // 127) + '%' if y == 1: # get icon for instrument font_name = './fonts/music-instruments' text = instruments[self.gear_values[counter]] label = Label(text=text, font_name=font_name, color=(0, 0, 0, 1), center_x=scaled_x, center_y=scaled_y, font_size=str(label_font_size) + 'sp') self.gear_labels.append(label) self.add_widget(label) counter += 1 for indx, loc in enumerate(self.music_box_gear_locations): gear_type = gear_type_map[indx % 4] gear_loc = GearLocation(loc, center_gear_size / 2, None, None, gear_type) self.gear_music_locations.append(gear_loc) self.canvas.add(gear_loc) def edit_goal_play_status(self, value): if self.use_tutorial: if self.goal_play_status == None and value == 'started': self.goal_play_status = 'started' elif self.goal_play_status == 'started' and value == 'finished': self.goal_play_status = 'finished' def _can_add_gear(self, new_gear): for gear in self.music_gears: if gear.type == new_gear.type: return False return True def _check_music_gears(self): all_types = ['volume', 'pitch', 'tempo', 'instrument'] for gear in self.music_gears: if gear.type in all_types: all_types.remove(gear.type) return len(all_types) == 0 def update_center_gear_on_layout(self, winsize): width, height = winsize center_gear_location = (width / 6 * 4.5, height / 4 * 2.5) center_gear_size = min(Window.width / 10, Window.height / 10) self.center_gear_center.update_storage_pos(center_gear_location) self.center_gear_center.update_music_pos(center_gear_location) self.center_gear.update_storage_pos(center_gear_location) self.center_gear.update_music_pos(center_gear_location) self.center_gear.on_layout(center_gear_location, center_gear_size) self.center_gear_center.on_layout(center_gear_location, center_gear_size / 2) tempo_location = (center_gear_location[0], center_gear_location[1] + center_gear_size + center_gear_size / 5) instrument_location = (center_gear_location[0], center_gear_location[1] - center_gear_size - center_gear_size / 5) pitch_location = (center_gear_location[0] + center_gear_size + center_gear_size / 5, center_gear_location[1]) volume_location = (center_gear_location[0] - center_gear_size - center_gear_size / 5, center_gear_location[1]) self.music_box_gear_locations = [ tempo_location, instrument_location, pitch_location, volume_location ] for indx, loc in enumerate(self.gear_music_locations): loc.on_layout(self.music_box_gear_locations[indx], center_gear_size / 2) def on_enter(self): if not self.use_tutorial and self.level == 1: while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def on_layout(self, winsize): mapped_dic = {'tempo': 0, 'instrument': 1, 'pitch': 2, 'volume': 3} self.update_center_gear_on_layout(winsize) self.size_dim = min(Window.width / 6, Window.height / 6) size = min(Window.width / 10, Window.height / 10) label_font_size = min(Window.width // 80, Window.height // 80) for loc in self.gear_storage_locations: scaled_x, scaled_y = self.get_scaled_x_y(winsize, loc.x, loc.y) loc.on_layout((scaled_x, scaled_y), size / 2) for indx, gear in enumerate(self.gears): scaled_x, scaled_y = self.get_scaled_x_y(winsize, gear.x, gear.y) gear.update_storage_pos((scaled_x, scaled_y)) gear.update_music_pos( self.music_box_gear_locations[mapped_dic[gear.type]]) gear.on_layout((scaled_x, scaled_y), size) self.gear_labels[indx].center_x = scaled_x self.gear_labels[indx].center_y = scaled_y self.gear_labels[indx].font_size = str(label_font_size) + 'sp' for center in self.gear_centers: scaled_x, scaled_y = self.get_scaled_x_y(winsize, center.x, center.y) center.update_storage_pos((scaled_x, scaled_y)) center.update_music_pos( self.music_box_gear_locations[mapped_dic[center.type]]) center.on_layout((scaled_x, scaled_y), size / 2) # update level label self.label.center_x = self.size_dim * 1.5 self.label.center_y = self.size_dim * 3 / 5 self.label.font_size = str(Window.width // 20) + 'sp' # update you win label self.you_win_label.center_x = (Window.width / 2) self.you_win_label.center_y = (Window.height / 2) self.you_win_label.font_size = str(Window.width // 10) + 'sp' self.tempo_label.center_x = (Window.width / 4) self.tempo_label.center_y = (Window.height / 5.25 * (0.5 + 0.5) + self.gear_area.position[1]) self.tempo_label.font_size = str(Window.width // 50) + 'sp' self.instrument_label.center_x = (Window.width / 4) self.instrument_label.center_y = (Window.height / 5.25 * (1.5 + 0.5) + self.gear_area.position[1]) self.instrument_label.font_size = str(Window.width // 50) + 'sp' self.pitch_label.center_x = (Window.width / 4) self.pitch_label.center_y = (Window.height / 5.25 * (2.5 + 0.5) + self.gear_area.position[1]) self.pitch_label.font_size = str(Window.width // 50) + 'sp' self.volume_label.center_x = (Window.width / 4) self.volume_label.center_y = (Window.height / 5.25 * (3.5 + 0.5) + self.gear_area.position[1]) self.volume_label.font_size = str(Window.width // 50) + 'sp' # update child components self.gear_area.on_layout((Window.width, Window.height)) self.music_box_area.on_layout((Window.width, Window.height)) self.options.on_layout((Window.width, Window.height)) # update particle effect and win/lose labels self.win_ps.emitter_x = Window.width / 2 self.win_ps.emitter_y = Window.height / 2 self.you_win_label.center_x = Window.width / 2 self.you_win_label.center_y = Window.height / 2 self.you_win_label.font_size = str(Window.width // 10) + 'sp' self.lose_ps.emitter_x = Window.width / 2 self.lose_ps.emitter_y = Window.height / 2 self.you_lose_label.center_x = Window.width / 2 self.you_lose_label.center_y = Window.height / 2 self.you_lose_label.font_size = str(Window.width // 10) + 'sp' # update error message location self.error_msg.center_x = Window.width / 2 self.error_msg.center_y = Window.height / 2 self.error_msg.font_size = str(Window.width // 20) + 'sp' # update tutorial overlays, label, and skip button if self.use_tutorial: self.update_tutorial_screen(self.tutorial_screen) self.tutorial_full_overlay.cpos = (Window.width / 2, Window.height / 2) self.tutorial_full_overlay.csize = (Window.width, Window.height) self.tutorial_options_overlay.size = (Window.width, self.size_dim + Window.height / 25) self.tutorial_musicbox_overlay.pos = (Window.width // 2, self.size_dim + Window.height / 25) self.tutorial_musicbox_overlay.size = ( Window.width / 2, Window.height - (self.size_dim + Window.height / 25)) self.tutorial_gearbox_overlay.pos = (0, self.size_dim + Window.height / 25) self.tutorial_gearbox_overlay.size = ( Window.width / 2, Window.height - (self.size_dim + Window.height / 25)) self.tutorial_skip_button.pos = ( 0.98 * Window.width - (self.skip_image.width * self.size_dim / 300), 0.98 * Window.height - self.skip_image.height * self.size_dim / 300) self.tutorial_skip_button.size = (self.skip_image.width * self.size_dim / 300, self.skip_image.height * self.size_dim / 300) def reset(self): for gear in self.gears: # reset gear position gear.reset() # stop gear from rotating gear.stop() # stop center gear from rotating self.center_gear.stop() # stop music self.goal_music_seq.stop() if self.gear_music_seq: self.gear_music_seq.stop() # end tutorial if self.use_tutorial: self.tutorial_label.text = '' self.use_tutorial = False self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def skip_tutorial_pressed(self, touch): if self.use_tutorial: if 0.98 * Window.width - ( self.skip_image.width * self.size_dim / 300 ) < touch.pos[ 0] < 0.98 * Window.width and 0.98 * Window.height - self.skip_image.height * self.size_dim / 300 < touch.pos[ 1] < 0.98 * Window.height: return True return False def on_touch_up(self, touch): # if click is on one of the lower menu buttons, perform the appropriate action self.options.on_touch_up(self.switch_to, self.gear_music_seq, self.check_level_complete(), self.win_ps, self.you_win_label, self.lose_ps, self.you_lose_label, self.reset, touch) for index, gear in enumerate(self.gears): # response is true if you click the current gear response = gear.on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) self.gear_centers[index].on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) if response: if gear not in self.music_gears: self.music_gears.append(gear) # update the gear music based on the gear that is selected function = 'self.gear_music.update_' + gear.type + '(' + str( gear.value) + ')' eval(function) else: if gear in self.music_gears: self.music_gears.remove(gear) self.center_gear.on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) if self.use_tutorial: # if skip button pressed, quit out of tutorial mode if self.skip_tutorial_pressed(touch): self.use_tutorial = False self.remove_widget(self.tutorial_label) self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) elif self.tutorial_screen == 'A': # show screen B (musicbox and gearbox covered) self.tutorial_screen = 'B' self.update_tutorial_screen('B') elif self.tutorial_screen == 'B': # show screen C (musicbox and options covered) self.tutorial_screen = 'C' self.update_tutorial_screen('C') elif self.tutorial_screen == 'C': # show screen D (gearbox and options covered) self.tutorial_screen = 'D' self.update_tutorial_screen('D') elif self.tutorial_screen == 'D': # show screen E (options covered) self.tutorial_screen = 'E' self.update_tutorial_screen('E') elif self.tutorial_screen == 'E' and self._check_music_gears(): # if all gears have been placed, show screen F (gearbox covered) self.tutorial_screen = 'F' self.update_tutorial_screen('F') elif self.tutorial_screen == 'F' and self.gear_play_status == 'finished' and self.goal_play_status == 'finished': # if both tunes have been played show screen G (gearbox and musicbox covered) self.tutorial_screen = 'G' self.update_tutorial_screen('G') elif self.tutorial_screen == 'G': # end tutorial self.use_tutorial = False self.remove_widget(self.tutorial_label) self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def update_tutorial_screen(self, screen): if not self.use_tutorial: return self.remove_widget(self.tutorial_label) if self.tutorial_screen == 'A': self.activate_overlays(['full']) self.tutorial_label.center_x = Window.width / 2 self.tutorial_label.center_y = Window.height / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Welcome to the\n[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/PassionOne-Regular]Play It By Gear Tutorial[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/lato-bold]\n\nThe goal of this game is to make the \n goal song match the song you create by \nplacing the correct gears in a music box \n\n[/font] [font=./fonts/lato-light] (click to see the next \nstep of the tutorial)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 40) + 'sp' if self.tutorial_screen == 'B': self.activate_overlays(['musicbox', 'gearbox']) self.tutorial_label.center_x = 1 / 2 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]\nAt the bottom of the screen is the menu\nbar which contains some helpful buttons\n\nthe [/font] [font=./fonts/options-icons]^[/font] [font=./fonts/lato-bold] button brings\nyou back to the level select menu\n\nthe [/font] [font=./fonts/options-icons]`[/font] [font=./fonts/lato-bold] button resets the level\n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 50) + 'sp' elif self.tutorial_screen == 'C': self.activate_overlays(['musicbox', 'options']) self.tutorial_label.center_x = 3 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]The left side of the screen\nhas all the gears you can choose\nfrom in order to make the\nmusic box sound correct\n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'D': self.activate_overlays(['gearbox', 'options']) self.tutorial_label.center_x = 1 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]The right side of the screen\nis the music box. Gears in\nthe music box modify the song.\n\nYou need one gear of each\ntype/color in the music box in\norder for the song to play. \n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'E': self.activate_overlays(['options']) self.tutorial_label.center_x = Window.width / 2 self.tutorial_label.center_y = (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Now drag one gear of each type/color into the music box\n[/font] [font=./fonts/lato-light](when there are 4 gears in the music box, the tutorial will continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'F': self.activate_overlays(['gearbox']) self.tutorial_label.center_x = 1 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Play the goal sound by pressing\nthe [/font] [font=./fonts/options-icons]_[/font] [font=./fonts/lato-bold] button, then press\nthe [/font] [font=./fonts/options-icons]~[/font] [font=./fonts/lato-bold] in the center gear to\nplay the song you created\nwith the gears\n\n[/font] [font=./fonts/lato-light](after you play both songs,\nclick again to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'G': self.activate_overlays(['musicbox', 'gearbox']) self.tutorial_label.center_x = 1 / 2 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Did the two songs sound the same?\n\nIf yes, you can press the [/font] [font=./fonts/options-icons]{[/font] [font=./fonts/lato-bold] button\n to see if you're correct\n\n If no, you can switch the gears in the music box\nuntil you think both songs sound the same\n\n[/font] [font=./fonts/lato-light](click to exit the tutorial)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 55) + 'sp' self.add_widget(self.tutorial_label) self.canvas.add(self.tutorial_skip_button) def on_touch_down(self, touch): other_gears = False for index, gear in enumerate(self.gears): cur_gear = gear.on_touch_down(touch.pos) other_gears = other_gears or cur_gear self.gear_centers[index].on_touch_down(touch.pos) if other_gears: if self.gear_music_seq: for gear in self.music_gears: gear.stop() self.center_gear.stop() self.gear_music_seq.stop() self.gear_music_seq = None else: response = self.center_gear.on_touch_down(touch.pos) if response: if self._check_music_gears(): for gear in self.music_gears: gear.toggle_rotate() is_rotating = self.center_gear.toggle_rotate() if is_rotating: self.gear_music_seq = self.gear_music.generate() self.gear_music_seq.start() if self.use_tutorial and self.gear_play_status == None: self.gear_play_status = 'started' else: if self.gear_music_seq: self.gear_music_seq.stop() self.gear_music_seq = None else: if not self.use_tutorial: self.error_msg.text = 'Place all 4 gears' else: self.error_msg.text = ' ' return self.error_msg.text = ' ' def on_touch_move(self, touch): for index, gear in enumerate(self.gears): gear.on_touch_move(touch.pos) self.gear_centers[index].on_touch_move(touch.pos) def on_update(self): self.goal_audio.on_update() self.gear_audio.on_update() self.options.on_update() for gear in self.gears: gear.on_update(1) self.center_gear.on_update(1) if self.gear_music_seq and not self.gear_music_seq.playing: for gear in self.music_gears: gear.stop() self.center_gear.stop() self.gear_music_seq = None if self.use_tutorial and self.gear_play_status == 'started': self.gear_play_status = 'finished' def check_level_complete(self): return self.goal_music.is_equal(self.gear_music)