from art import UV_FLIPX, UV_FLIPY, UV_ROTATE180 from ui_button import UIButton from ui_colors import UIColors from ui_element import UIElement from ui_menu_pulldown_item import PulldownMenuData, PulldownMenuItem, SeparatorItem class MenuItemButton(UIButton): dimmed_fg_color = UIColors.medgrey dimmed_bg_color = UIColors.lightgrey def hover(self): UIButton.hover(self) # keyboard nav if hovering with mouse for i, button in enumerate(self.element.buttons): if button is self: self.element.keyboard_nav_index = i self.element.update_keyboard_hover() break def click(self): UIButton.click(self) if self.item.close_on_select: self.element.ui.menu_bar.close_active_menu() class PulldownMenu(UIElement): "element that's moved and resized based on currently active pulldown" label_shortcut_padding = 5 visible = False always_consume_input = True bg_color = UIColors.lightgrey border_color = UIColors.medgrey border_corner_char = 77 border_horizontal_line_char = 78 border_vertical_line_char = 79 mark_char = 131 all_modes_visible = True support_keyboard_navigation = True keyboard_nav_left_right = True def open_at(self, menu_button, reset_keyboard_nav_index=True): # set X and Y based on calling menu button's location self.tile_x = menu_button.x self.tile_y = menu_button.y + 1 # determine pulldown width from longest item label length self.tile_width = 1 # save shortcuts as we got through shortcuts = [] callbacks = [] # if menu data has an item generator, use it to add to the existing list # copy list from class though; this function will run many times items = menu_button.menu_data.items[:] if menu_button.menu_data.get_items is not PulldownMenuData.get_items: items += menu_button.menu_data.get_items(self.ui.app) for item in items: shortcut, command = self.get_shortcut(item) shortcuts.append(shortcut) callbacks.append(command) # get label, static or dynamic label = item.label if item.get_label is not PulldownMenuItem.get_label: label = item.get_label(self.ui.app) # item might have already calculated padding, ie from get_items if item.no_pad: item_width = len(label) + 2 # get full width of item, label shortcut and some padding else: item_width = len(label) + self.label_shortcut_padding item_width += len(shortcut) if item_width > self.tile_width: self.tile_width = item_width self.tile_height = len(items) + 2 self.art.resize(self.tile_width, self.tile_height) # draw fg = self.ui.colors.black self.art.clear_frame_layer(0, 0, self.bg_color, fg) self.draw_border(menu_button) # create as many buttons as needed, set their sizes, captions, callbacks self.buttons = [] for i, item in enumerate(items): # skip button creation for separators, just draw a line if item is SeparatorItem: for x in range(1, self.tile_width - 1): self.art.set_tile_at( 0, 0, x, i + 1, self.border_horizontal_line_char, self.border_color, ) continue button = MenuItemButton(self) # give button a handle to its item button.item = item full_label = item.label if item.get_label is not PulldownMenuItem.get_label: # trim output full_label = item.get_label(self.ui.app) if not item.no_pad: full_label += shortcuts[i].rjust(self.tile_width - 2 - len(full_label)) button.caption = full_label button.width = len(full_label) button.x = 1 button.y = i + 1 button.callback = callbacks[i] if item.cb_arg is not None: button.cb_arg = item.cb_arg # dim items that aren't applicable to current app state if not item.always_active and item.should_dim(self.ui.app): button.set_state("dimmed") button.can_hover = False self.buttons.append(button) # set our X and Y, draw buttons, etc self.reset_loc() self.reset_art() # if this menu has special logic for marking items, use it if ( menu_button.menu_data.should_mark_item is not PulldownMenuData.should_mark_item ): for i, item in enumerate(items): if menu_button.menu_data.should_mark_item(item, self.ui): self.art.set_char_index_at(0, 0, 1, i + 1, self.mark_char) # reset keyboard nav state for popups if reset_keyboard_nav_index: self.keyboard_nav_index = 0 self.keyboard_navigate(0, 0) self.visible = True self.ui.keyboard_focus_element = self def draw_border(self, menu_button): "draws a fancy lil frame around the pulldown's edge" fg = self.border_color char = self.border_horizontal_line_char # top/bottom edges for x in range(self.tile_width): self.art.set_tile_at(0, 0, x, 0, char, fg) self.art.set_tile_at(0, 0, x, self.tile_height - 1, char, fg) # left/right edges char = self.border_vertical_line_char for y in range(self.tile_height): self.art.set_tile_at(0, 0, 0, y, char, fg) self.art.set_tile_at(0, 0, self.tile_width - 1, y, char, fg) # corners: bottom left, bottom right, top right char = self.border_corner_char x, y = 0, self.tile_height - 1 xform = UV_FLIPY self.art.set_tile_at(0, 0, x, y, char, fg, None, xform) x = self.tile_width - 1 xform = UV_ROTATE180 self.art.set_tile_at(0, 0, x, y, char, fg, None, xform) y = 0 xform = UV_FLIPX self.art.set_tile_at(0, 0, x, y, char, fg, None, xform) # gap beneath menu bar button for x in range(1, len(menu_button.caption) + 2): self.art.set_tile_at(0, 0, x, 0, 0) self.art.set_tile_at(0, 0, x, 0, char, fg, None, UV_FLIPY) def get_shortcut(self, menu_item): # get InputLord's binding from given menu item's command name, # return concise string for bind and the actual function it runs. def null(): pass # special handling of SeparatorMenuItem, no command or label if menu_item is SeparatorItem: return "", null binds = self.ui.app.il.edit_binds for bind_tuple in binds: command_functions = binds[bind_tuple] for f in command_functions: if f.__name__ == "BIND_{}".format(menu_item.command): shortcut = "" # shift, alt, ctrl if bind_tuple[1]: shortcut += "Shift-" if bind_tuple[2]: shortcut += "Alt-" if bind_tuple[3]: # TODO: cmd vs ctrl for mac vs non shortcut += "C-" # bind strings that start with _ will be disregarded if not (bind_tuple[0].startswith("_") and len(bind_tuple[0]) > 1): shortcut += bind_tuple[0] return shortcut, f self.ui.app.log("Shortcut/command not found: {}".format(menu_item.command)) return "", null