class Popup(): "a popup" def __init__(self, brick, title, contentType): self.brick = brick self.title = title self.titleFont = Font(family='arial', size=15, bold=True) self.normalFont = Font(family='arial', size=13) self.contentType = contentType def open(self, position=[]): self.draw(position=position) def close(self, position): position.insert(0, -1) def draw(self): self.brick.screen.draw_image(9, 11, 'assets/graphics/misc/popup.png', transparent=Color.RED) self.brick.screen.set_font(self.titleFont) self.brick.screen.draw_text( self.brick.screen.width / 2 - self.titleFont.text_width(self.title) / 2, 15, self.title) self.brick.screen.draw_line( self.brick.screen.width / 2 - self.titleFont.text_width(self.title) / 2 - 1, 31, self.brick.screen.width / 2 + self.titleFont.text_width(self.title) / 2 + 1, 31, width=2)
class UIManager: """ Basicly a Menu """ def __init__(self, config, settings, brick, logger, settingsPath, charlieOSX): # general variable setup logger.info(self, 'Starting UI initialisation') self.__config = config self.__settings = settings self.__click = 'assets/media/click.wav' self.__confirm = 'assets/media/confirm.wav' self.__settingsPath = settingsPath self.brick = brick self.logger = logger self.os = charlieOSX self.profileHelper = ProfileHelper(self.logger, self.__config) self.__sound_lock = _thread.allocate_lock() self.__almostBigFont = Font(family='Arial', size=12, bold=False) self.__bigFont = Font(family='Arial', size=15, bold=True) self.position = [0, 0, False] # Main Menu self.mainMenu = Menu('sidebar') mainPages = [ "assets/graphics/menus/programming.png", "assets/graphics/menus/testing.png", "assets/graphics/menus/remote.png", "assets/graphics/menus/competition.png", "assets/graphics/menus/settings.png", ] for i in range(len(mainPages)): name = mainPages[i].split('/')[3].split('.')[0] self.mainMenu.addObject( UIObject(name, self.brick, Box(0, i, 30, 25), 'img', mainPages[i], padding=(0, 0, True))) # Programming Menu self.programming = Menu('list', self.brick) self.programming.setList(self.__config['profileNames']) self.programming.setClickAction(self.runProgramming) # Testing Menu self.testing = Menu('list', self.brick) self.testing.setList(self.__config['profileNames']) self.testing.setClickAction(self.runTesting) # Remote-Control Menu self.remote = Menu('canvas') self.remote.addObject( UIObject('startButton', self.brick, Box(58, 80, 82, 14), 'textBox', 'Start Webremote', padding=(-1, -1, False))) self.remote.addObject( UIObject('endButton', self.brick, Box(59, 80, 81, 14), 'textBox', 'Stop Webremote', padding=(-1, -1, False), visible=False)) self.remote.getObjectByName('startButton').setClickAction( self.runWebremote) # Competition-Mode Menu self.competition = Menu('canvas') self.competition.addObject( UIObject('startButton', self.brick, Box(55, 80, 88, 14), 'textBox', 'Start Competition', padding=(-1, -1, False))) self.competition.addObject( UIObject('runButton', self.brick, Box(74, 90, 48, 14), 'textBox', 'Start Run', padding=(-1, -1, False), visible=False)) self.competition.addObject( UIObject('nextButton', self.brick, Box(72, 90, 53, 14), 'textBox', 'Start Next', padding=(-1, -1, False), visible=False)) self.competition.getObjectByName('startButton').setClickAction( self.runCompetition) # Settings Menu self.settingsMenu = Menu('dict', self.brick) self.settingsMenu.setDict(self.__settings) # menu Variables self.loop = True self.currentMenu = self.mainMenu self.subMenus = [ self.programming, self.testing, self.remote, self.competition, self.settingsMenu ] self.types = { 4: 'Turn', 5: 'Action' if not self.__config['useGearing'] else 'Gearing', 7: 'Straight', 9: 'Intervall', 11: 'Curve', 12: 'to Color', 15: 'to Wall' } self.secondParam = { 4: 'Angle', 5: 'Revs', 7: 'Distance', 9: 'Distance', 11: 'Distance', 12: 'Color', 15: 'none', 'units': { 4: '°', 5: 'Revs', 7: 'cm', 9: 'cm', 11: 'cm', 12: '', 15: '' } } self.thirdParam = { 4: 'Port', 5: 'Port', 7: 'none' if self.__config['robotType'] != 'MECANUM' else 'Angle', 9: 'Amount', 11: 'Angle', 12: 'Side', 15: 'none', 'units': { 4: '', 5: '', 7: '°', 9: '', 11: '°', 12: '', 15: '' } } self.valueTypes = { 1: { 4: 'percentage', 5: 'percentage', 7: 'percentage', 9: 'percentage', 11: 'percentage', 12: 'percentage', 15: 'percentage' }, 2: { 4: 'largeInt', 5: 'largeInt', 7: 'largeInt', 9: 'largeInt', 11: 'largeInt', 12: 'bool', 15: 'none' }, 3: { 4: 'side', 5: 'port', 7: 'none', 9: 'largeInt', 11: 'largeInt', 12: 'side', 15: 'none' } } self.valueRanges = { 'percentage': range(0, 101), 'type': [7, 4, 5, 11, 9, 12, 15], 'largeInt': range(0, 10000), 'side': [2, 3, 23], 'bool': [0, 1], 'port': range(0, 4), 'none': [0] } #self.logger.info(self, 'UI initialized') def __str__(self): return "UIManager" def __sound(self, file): ''' This private method is used for playing a sound in a separate thread so that other code can be executed simultaneously. Args: file (str / SoundFile): The path to the soundfile to play ''' def __playSoundFile(soundFile): with self.__sound_lock: self.brick.speaker.play_file(soundFile) _thread.start_new_thread(__playSoundFile, (file, )) def mainLoop(self): # Welcome screen self.brick.screen.draw_image(0, 0, 'assets/graphics/menus/mainMenu.png', transparent=Color.RED) while not any(self.brick.buttons.pressed()): pass if Button.UP in self.brick.buttons.pressed(): self.position[1] = self.currentMenu.maxY elif Button.DOWN in self.brick.buttons.pressed(): self.position[1] = 0 self.currentMenu.draw(self.position) print(self.position) time.sleep(0.3) self.menuLocations = [ "assets/graphics/animations/mainProgram/10.png", "assets/graphics/animations/mainTest/10.png", "assets/graphics/animations/mainRemote/10.png", "assets/graphics/animations/mainCompetition/10.png", "assets/graphics/animations/mainSettings/10.png", ] while self.loop: if any(self.brick.buttons.pressed()): if Button.UP in self.brick.buttons.pressed( ) and not self.position[2]: self.position[1] = self.position[1] - 1 if self.position[ 1] > 0 else self.currentMenu.maxY elif Button.DOWN in self.brick.buttons.pressed( ) and not self.position[2]: self.position[1] = self.position[1] + 1 if self.position[ 1] < self.currentMenu.maxY else 0 elif Button.LEFT in self.brick.buttons.pressed() and len( self.position) > 3 and not self.position[2]: if self.currentMenu.getType( ) == 'canvas' and self.position[0] > 0: self.position[ 0] = self.position[0] - 1 if self.position[ 0] > 0 else self.currentMenu.maxX else: self.position.pop(0) self.position.pop(0) self.position.pop(0) if len(self.position) == 3: self.animate(self.position[1], False) self.currentMenu = self.mainMenu elif Button.RIGHT in self.brick.buttons.pressed( ) and not self.position[2]: # self.position[0] = self.position[0] + 1 if self.position[0] < self.currentMenu.maxX else 0 if self.currentMenu.getType() == 'canvas': self.position[ 0] = self.position[0] + 1 if self.position[ 0] < self.currentMenu.maxX else 0 else: if len(self.position) == 3: self.animate(self.position[1], True) self.currentMenu = self.subMenus[self.position[1]] self.position.insert(0, False) self.position.insert(0, 0) self.position.insert(0, 0) elif Button.RIGHT in self.brick.buttons.pressed( ) and self.position[2]: self.position[0] = self.position[0] + 1 if self.position[ 0] < self.currentMenu.maxX else 0 self.__settings['options'][list( self.__settings['options'].keys())[ self.position[1]]] = self.position[0] elif Button.LEFT in self.brick.buttons.pressed( ) and self.position[2]: self.position[0] = self.position[0] - 1 if self.position[ 0] > 0 else self.currentMenu.maxX self.__settings['options'][list( self.__settings['options'].keys())[ self.position[1]]] = self.position[0] elif Button.CENTER in self.brick.buttons.pressed(): if self.currentMenu.getType() == 'list': self.currentMenu.click(self.position) elif self.currentMenu.getType() == 'dict': if self.position[2]: self.position.pop(0) self.position.pop(0) self.position.pop(0) self.position[2] = not self.position[2] self.os.storeSettings(self.__settings, 'default') self.os.applySettings(self.__settings) else: self.position[2] = not self.position[2] self.position.insert(0, True) self.position.insert(0, self.position[2]) self.position.insert( 0, self.__settings['options'][list( self.__settings['options'].keys())[ self.position[0]]]) elif len(self.position) != 3: self.position[2] = not self.position[2] self.currentMenu.getObjectByPostion( self.position).click() if self.currentMenu.getType() not in ['canvas']: self.position.insert(0, False) self.position.insert(0, 0) self.position.insert(0, 0) if self.position[0] == -1: self.position.pop(0) self.brick.screen.draw_image( 0, 0, self.menuLocations[self.position[len(self.position) - 4]], transparent=Color.RED) self.currentMenu.draw(self.position) print(self.position) time.sleep(0.3) def animate(self, state, direction): ''' Animates the transition between the main-menu-pages and the submenu-pages Args: state (int): The menu-transition to animate direction (bool): wether it should play the animation forwards or backwards ''' menus = [ 'mainProgram', 'mainTest', 'mainRemote', 'mainCompetition', 'mainSettings' ] if direction: try: for i in range(1, 11): self.brick.screen.draw_image( 0, 0, 'assets/graphics/animations/%s/%s.png' % (menus[state], i), transparent=Color.RED) time.sleep(0.05) except Exception as exception: self.logger.error(self, "Could not animate menu: ", str(exception)) else: try: for i in reversed(range(1, 11)): self.brick.screen.draw_image( 0, 0, 'assets/graphics/animations/%s/%s.png' % (menus[state], i), transparent=Color.RED) time.sleep(0.05) except Exception as exception: self.logger.error(self, "Could not animate menu: ", str(exception)) def runProgramming(self, position): index = position[1] content = self.profileHelper.getProfileData( self.__config['profileNames'][index]) self.runList = Menu('progList', self.brick) self.runList.setList(content) self.runList.setClickAction(self.runEditing) self.runList.draw(self.position) # sideloop for interactive sub submenu while not Button.LEFT in self.brick.buttons.pressed(): if any(self.brick.buttons.pressed()): if Button.UP in self.brick.buttons.pressed( ) and not self.position[2]: self.position[1] = self.position[1] - 1 if self.position[ 1] > 0 else self.runList.maxY elif Button.DOWN in self.brick.buttons.pressed( ) and not self.position[2]: self.position[1] = self.position[1] + 1 if self.position[ 1] < self.runList.maxY else 0 elif Button.CENTER in self.brick.buttons.pressed(): startTime = time.time() deleted = False while Button.CENTER in self.brick.buttons.pressed(): if time.time() - startTime > 2 and not deleted: deleted = True content.pop() content.pop(self.position[1]) self.runList.setList(content) self.runList.draw(self.position) self.brick.speaker.beep() buttonTime = time.time() - startTime if buttonTime < 2: # edit step if self.position[1] != len(content) - 1: self.runList.click(self.position) else: content.pop() content.append([7, 100, 10, 0]) self.runList.setList(content) self.profileHelper.setProfileData( self.__config['profileNames'][index], content) if self.position[0] == -1: self.position.pop(0) self.brick.screen.draw_image( 0, 0, self.menuLocations[self.position[len(self.position) - 4]], transparent=Color.RED) print(self.position, content) self.runList.draw(self.position) time.sleep(0.3) def runEditing(self, position): def formatScreenContent(thirdType): screenContent[0] = 'Type: %s' % self.types[content[index][0]] screenContent[1] = 'Speed: %s%%' % content[index][1] screenContent[2] = '%s: %s%s' % ( self.secondParam[content[index][0]], content[index][2], self.secondParam['units'][content[index][0]] ) if self.secondParam[content[index][0]] != 'none' else '' value = str(content[index][3]).replace('23', 'Left/Right').replace( '2', 'Left').replace( '3', 'Right') if thirdType == 'side' else content[index][3] screenContent[3] = '%s: %s%s' % ( self.thirdParam[content[index][0]], value, self.thirdParam['units'][content[index][0]] ) if self.thirdParam[content[index][0]] != 'none' else '' smallStep = 1 bigStep = 5 index = position[1] screenContent = ['', '', '', ''] content = self.profileHelper.getProfileData( self.__config['profileNames'][position[4]]) formatScreenContent(self.valueTypes[3][content[index][0]]) menu = ProgrammingWindow(self.brick, 'Edit Step', 'list', screenContent) self.position.insert(0, False) self.position.insert(0, 0) self.position.insert(0, 0) mmax = 3 menu.open(position) while not (Button.LEFT in self.brick.buttons.pressed() and not position[2]): if any(self.brick.buttons.pressed()): valueType = self.valueTypes[position[1]][ content[index][0]] if position[1] != 0 else 'type' valueRange = self.valueRanges[valueType] if Button.UP in self.brick.buttons.pressed(): if self.position[2]: if valueType not in ['type', 'side']: content[index][position[1]] = content[index][position[ 1]] + smallStep if content[index][position[ 1]] + smallStep in valueRange else valueRange[ 0] else: try: content[index][position[1]] = valueRange[ valueRange. index(content[index][position[1]]) - 1 if valueRange. index(content[index][position[1]]) - 1 >= 0 else len(valueRange) - 1] except ValueError: content[index][position[1]] = valueRange[0] else: self.position[1] = self.position[ 1] - 1 if self.position[1] > 0 else mmax elif Button.DOWN in self.brick.buttons.pressed(): if self.position[2]: if valueType not in ['type', 'side']: content[index][position[1]] = content[index][position[ 1]] - smallStep if content[index][position[ 1]] - smallStep in valueRange else valueRange[ len(valueRange) - 1] else: try: content[index][position[1]] = valueRange[ valueRange. index(content[index][position[1]]) + 1 if valueRange. index(content[index][position[1]]) + 1 < len(valueRange) else 0] except ValueError: content[index][position[1]] = valueRange[0] else: self.position[1] = self.position[ 1] + 1 if self.position[1] < mmax else 0 elif Button.RIGHT in self.brick.buttons.pressed(): if self.position[2]: if valueType not in ['type', 'side']: content[index][position[1]] = content[index][ position[1]] + bigStep if content[index][position[ 1]] + bigStep in valueRange else valueRange[ 0] elif Button.LEFT in self.brick.buttons.pressed(): if self.position[2]: if valueType not in ['type', 'side']: content[index][position[1]] = content[index][ position[1]] - bigStep if content[index][position[ 1]] - bigStep in valueRange else valueRange[ len(valueRange) - 1] elif Button.CENTER in self.brick.buttons.pressed(): position[2] = not position[2] formatScreenContent(self.valueTypes[3][content[index][0]]) menu.updateContent(screenContent) menu.draw(position=position) if not (Button.LEFT in self.brick.buttons.pressed() and not position[2]): time.sleep(0.3) self.position.pop(0) self.position.pop(0) self.position.pop(0) self.profileHelper.setProfileData( self.__config['profileNames'][position[4]], content) menu.close(position) time.sleep(0.3) def runTesting(self, position): index = position[1] profileData = self.profileHelper.getProfileData( self.__config['profileNames'][index]) time.sleep(0.3) self.os.robot.execute(profileData) def runWebremote(self): self.currentMenu.getObjectByName('startButton').setVisibility(False) self.currentMenu.getObjectByName('endButton').setVisibility(True) self.position[1] += 1 self.currentMenu.draw(self.position) time.sleep(0.3) self.os.webremote.run() self.currentMenu.getObjectByName('startButton').setVisibility(True) self.currentMenu.getObjectByName('endButton').setVisibility(False) self.position[2] = False self.position[1] -= 1 self.currentMenu.draw(self.position) time.sleep(0.3) def runCompetition(self): dataArray = [] self.wireless( False ) # uncomment to not loose connectivity for console-logging and effective development through wifi self.currentMenu.getObjectByName('startButton').setVisibility(False) self.currentMenu.getObjectByName('runButton').setVisibility(True) self.position[0], self.position[1] = 1, 1 self.currentMenu.draw(self.position) for i in self.__config['profileNames']: dataArray.append(self.profileHelper.getProfileData(i)) self.currentMenu.getObjectByName('runButton').setVisibility(False) self.currentMenu.getObjectByName('nextButton').setVisibility(True) time.sleep(0.3) for index in range(0, len(dataArray)): self.brick.screen.draw_box(30, 30, 200, 80, fill=True, color=Color.WHITE) self.brick.screen.set_font(self.__almostBigFont) self.brick.screen.draw_text(74, 36, "Run %s/%s:" % (index, len(dataArray) - 1), background_color=Color.WHITE) self.brick.screen.set_font(self.__bigFont) self.brick.screen.draw_text(98 - self.__bigFont.text_width( self.__config['profileNames'][index]) / 2, 60, self.__config['profileNames'][index], background_color=Color.WHITE) while Button.CENTER not in self.brick.buttons.pressed(): pass self.position[0], self.position[1] = 1, 1 self.currentMenu.draw(self.position) time.sleep(0.3) self.os.robot.execute(dataArray[index]) time.sleep(0.3) self.position[0], self.position[1] = 1, 0 self.currentMenu.draw(self.position) self.wireless(True) self.brick.screen.draw_box(30, 30, 200, 80, fill=True, color=Color.WHITE) self.position[0], self.position[1], self.position[2] = 0, 0, False self.currentMenu.getObjectByName('startButton').setVisibility(True) self.currentMenu.getObjectByName('runButton').setVisibility(False) self.currentMenu.getObjectByName('nextButton').setVisibility(False) self.currentMenu.draw(self.position) def wireless(self, enable): cmd = 'enable' if not enable else 'disable' os.system('connmanctl {} offline'.format(cmd))
def error(self, method, msg, exception): ''' This method formats the given inputs as <[time] [MethodName] [Wrror] msg>. The output of this fuction can be disabled by setting the "Logging-Level" in the settings to 4 or higher. Also dependent on wether or not "Show Errors" is disabled or not in the settings, this function will create a 'popup' on-screen with the given warn-message. Args: method (obj): A passthrough of the object calling this fuction msg (str): The error message exception (exception / str): string or exception of the error that occured ''' if self.__settings['options']['Logging-level'] <= 3: print( self.getFormattedTime(), '[%s] [Error] %s: %s: %s' % (str(method), msg, type(exception).__name__, str(exception))) self.__logFile.write('%s [%s] [Error] %s: %s: %s\n' % (self.getFormattedTime(), str(method), msg, type(exception).__name__, str(exception))) if self.__settings['options']['Show Errors']: exception = str(exception) self.__sound(SoundFile.GENERAL_ALERT) self.__brick.screen.draw_image( 26, 24, 'assets/graphics/notifications/error.png', transparent=Color.RED) self.__brick.screen.set_font(Font(family='arial', size=7)) if Font.text_width(Font(family='arial', size=7), exception) <= 90: self.__brick.screen.draw_text(32, 47, exception, text_color=Color.BLACK) elif len(exception) <= 30 * 2: exception1, exception2 = exception[:27], exception[27:] self.__brick.screen.draw_text(32, 47, exception1, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 57, exception2, text_color=Color.BLACK) else: exception1, exception2, exception3 = exception[:27], exception[ 27:53], exception[53:] self.__brick.screen.draw_text(32, 47, exception1, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 57, exception2, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 67, exception3, text_color=Color.BLACK) #wait for user to press middle button while not Button.CENTER in self.__brick.buttons.pressed(): pass self.__brick.screen.draw_image( 26, 24, 'assets/graphics/notifications/errorSel.png', transparent=Color.RED) #wait for user letting button go while Button.CENTER in self.__brick.buttons.pressed(): pass self.__refreshScreenNeeded = 1
def warn(self, method, msg): ''' This method formats the given inputs as <[time] [MethodName] [Warn] msg>. The output of this fuction can be disabled by setting the "Logging-Level" in the settings to 3 or higher. Also dependent on wether or not "Show Warnings" is disabled or not in the settings, this function will create a 'popup' on-screen with the given warn-message. Args: method (obj): A passthrough of the object calling this fuction msg (str): The warn message ''' if self.__settings['options']['Logging-level'] <= 2: print(self.getFormattedTime(), '[%s] [Warning]' % str(method), msg) self.__logFile.write('%s [%s] [Warning] %s\n' % (self.getFormattedTime(), str(method), msg)) if self.__settings['options']['Show Warnings']: self.__sound(SoundFile.GENERAL_ALERT) self.__brick.screen.draw_image( 26, 24, 'assets/graphics/notifications/warn.png', transparent=Color.RED) self.__brick.screen.draw_text(31, 34, msg, text_color=Color.BLACK) if Font.text_width(Font(family='arial', size=7), exception) <= 90: self.__brick.screen.draw_text(32, 47, msg, text_color=Color.BLACK) elif len(exception) <= 30 * 2: msg1, msg2 = msg[:27], msg[27:] self.__brick.screen.draw_text(32, 47, msg1, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 57, msg2, text_color=Color.BLACK) else: msg1, msg2, msg3 = exception[:27], exception[27:53], exception[ 53:] self.__brick.screen.draw_text(32, 47, msg1, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 57, msg2, text_color=Color.BLACK) self.__brick.screen.draw_text(32, 67, msg3, text_color=Color.BLACK) #wait for user to press middle button while not Button.CENTER in self.__brick.buttons.pressed(): pass self.__brick.screen.draw_image( 26, 24, 'assets/graphics/notifications/warnSel.png', transparent=Color.RED) #wait for user letting button go while Button.CENTER in self.__brick.buttons.pressed(): pass self.__refreshScreenNeeded = 1