from renderable import LAYER_VIS_DIM, LAYER_VIS_FULL, LAYER_VIS_NONE from ui_tool import ( EraseTool, FillTool, GrabTool, PasteTool, PencilTool, RotateTool, SelectTool, TextTool, ) # # specific pulldown menu items, eg File > Save, Edit > Copy # class PulldownMenuItem: # label that displays for this item label = "Test Menu Item" # bindable command we look up from InputLord to get binding text from command = "test_command" # if not None, passed to button's cb_arg cb_arg = None # if True, pulldown button creation process won't auto-pad no_pad = False # if True, item will never be dimmed always_active = False # if True, pulldown will close when this item is selected close_on_select = False # item is allowed in Art Mode art_mode_allowed = True # item is allowed in Game Mode game_mode_allowed = True def should_dim(app): "returns True if this item should be dimmed based on current application state" # so many commands are inapplicable with no active art, default to dimming an # item if this is the case return app.ui.active_art is None def get_label(app): "returns custom generated label based on app state" return None class SeparatorItem(PulldownMenuItem): "menu separator, non-interactive and handled specially by menu drawing" pass class ArtModePulldownMenuItem(PulldownMenuItem): # unless overridden, art mode items not allowed in game mode game_mode_allowed = False # # file menu # class FileNewItem(ArtModePulldownMenuItem): label = "New…" command = "new_art" always_active = True class FileOpenItem(ArtModePulldownMenuItem): label = "Open…" command = "open_art" always_active = True class FileSaveItem(ArtModePulldownMenuItem): label = "Save" command = "save_current" def should_dim(app): return not app.ui.active_art or not app.ui.active_art.unsaved_changes class FileSaveAsItem(ArtModePulldownMenuItem): label = "Save As…" command = "save_art_as" def should_dim(app): return app.ui.active_art is None class FileCloseItem(ArtModePulldownMenuItem): label = "Close" command = "close_art" def should_dim(app): return app.ui.active_art is None class FileRevertItem(ArtModePulldownMenuItem): label = "Revert" command = "revert_art" def should_dim(app): return app.ui.active_art is None or not app.ui.active_art.unsaved_changes class FileImportItem(ArtModePulldownMenuItem): label = "Import…" command = "import_file" always_active = True class FileExportItem(ArtModePulldownMenuItem): label = "Export…" command = "export_file" def should_dim(app): return app.ui.active_art is None class FileExportLastItem(ArtModePulldownMenuItem): label = "Export last" command = "export_file_last" def should_dim(app): return app.ui.active_art is None class FileConvertImageItem(ArtModePulldownMenuItem): label = "Convert Image…" command = "convert_image" def should_dim(app): return app.ui.active_art is None class FileQuitItem(ArtModePulldownMenuItem): label = "Quit" command = "quit" always_active = True game_mode_allowed = True # # edit menu # class EditUndoItem(ArtModePulldownMenuItem): label = "Undo" command = "undo" def should_dim(app): return ( not app.ui.active_art or len(app.ui.active_art.command_stack.undo_commands) == 0 ) class EditRedoItem(ArtModePulldownMenuItem): label = "Redo" command = "redo" def should_dim(app): return ( not app.ui.active_art or len(app.ui.active_art.command_stack.redo_commands) == 0 ) class EditCutItem(ArtModePulldownMenuItem): label = "Cut" command = "cut_selection" def should_dim(app): return len(app.ui.select_tool.selected_tiles) == 0 class EditCopyItem(ArtModePulldownMenuItem): label = "Copy" command = "copy_selection" def should_dim(app): return len(app.ui.select_tool.selected_tiles) == 0 class EditPasteItem(ArtModePulldownMenuItem): label = "Paste" command = "select_paste_tool" def should_dim(app): return len(app.ui.clipboard) == 0 class EditDeleteItem(ArtModePulldownMenuItem): label = "Clear" command = "erase_selection_or_art" class EditSelectAllItem(ArtModePulldownMenuItem): label = "Select All" command = "select_all" class EditSelectNoneItem(ArtModePulldownMenuItem): label = "Select None" command = "select_none" class EditSelectInvertItem(ArtModePulldownMenuItem): label = "Invert Selection" command = "select_invert" class EditPreferences(ArtModePulldownMenuItem): label = "Preferences…" command = "edit_cfg" # # tool menu # class ToolTogglePickerItem(ArtModePulldownMenuItem): # two spaces in front of each label to leave room for mark label = "Show char/color picker" command = "toggle_picker" class ToolTogglePickerHoldItem(ArtModePulldownMenuItem): label = "blah" command = "toggle_picker_hold" def get_label(app): return "Picker toggle key: {}".format( ["press", "hold"][app.ui.popup_hold_to_show] ) class ToolSwapSelectedColors(ArtModePulldownMenuItem): label = "Swap selected fg/bg colors" command = "swap_fg_bg_colors" class ToolToggleArtToolbar(ArtModePulldownMenuItem): label = " Show toolbar" command = "toggle_art_toolbar" def should_mark(ui): return ui.art_toolbar.visible class ToolPaintItem(ArtModePulldownMenuItem): # two spaces in front of each label to leave room for mark label = " {}".format(PencilTool.button_caption) command = "select_pencil_tool" class ToolEraseItem(ArtModePulldownMenuItem): label = " {}".format(EraseTool.button_caption) command = "select_erase_tool" class ToolRotateItem(ArtModePulldownMenuItem): label = " {}".format(RotateTool.button_caption) command = "select_rotate_tool" class ToolGrabItem(ArtModePulldownMenuItem): label = " {}".format(GrabTool.button_caption) command = "select_grab_tool" class ToolTextItem(ArtModePulldownMenuItem): label = " {}".format(TextTool.button_caption) command = "select_text_tool" class ToolSelectItem(ArtModePulldownMenuItem): label = " {}".format(SelectTool.button_caption) command = "select_select_tool" class ToolPasteItem(ArtModePulldownMenuItem): label = " {}".format(PasteTool.button_caption) command = "select_paste_tool" class ToolFillItem(ArtModePulldownMenuItem): label = " {}".format(FillTool.button_caption) command = "select_fill_tool" class ToolIncreaseBrushSizeItem(ArtModePulldownMenuItem): label = "blah" command = "increase_brush_size" def should_dim(app): # dim this item for tools where brush size doesn't apply if not app.ui.active_art or not app.ui.selected_tool.brush_size: return True def get_label(app): if not app.ui.selected_tool.brush_size: return "Increase brush size" size = app.ui.selected_tool.brush_size + 1 return "Increase brush size to {}".format(size) class ToolDecreaseBrushSizeItem(ArtModePulldownMenuItem): label = "blah" command = "decrease_brush_size" def should_dim(app): if not app.ui.active_art or not app.ui.selected_tool.brush_size: return True return app.ui.selected_tool.brush_size <= 1 def get_label(app): if not app.ui.selected_tool.brush_size: return "Decrease brush size" size = app.ui.selected_tool.brush_size - 1 return "Decrease brush size to {}".format(size) class ToolSettingsItem(ArtModePulldownMenuItem): # base class for tool settings toggle items def should_dim(app): # blacklist specific tools return not app.ui.active_art or type(app.ui.selected_tool) in [SelectTool] class ToolToggleAffectsCharItem(ToolSettingsItem): label = " Affects: character" command = "toggle_affects_char" def should_mark(ui): return ui.selected_tool.affects_char class ToolToggleAffectsFGItem(ToolSettingsItem): label = " Affects: foreground color" command = "toggle_affects_fg" def should_mark(ui): return ui.selected_tool.affects_fg_color class ToolToggleAffectsBGItem(ToolSettingsItem): label = " Affects: background color" command = "toggle_affects_bg" def should_mark(ui): return ui.selected_tool.affects_bg_color class ToolToggleAffectsXformItem(ToolSettingsItem): label = " Affects: character xform" command = "toggle_affects_xform" def should_mark(ui): return ui.selected_tool.affects_xform # cycle between the 3 different fill boundary types class ToolSetFillBoundariesItem(ArtModePulldownMenuItem): label = "blah" command = "cycle_fill_boundary_mode" def should_dim(app): return type(app.ui.selected_tool) is not FillTool def get_label(app): return "Fill tool bounded by: {}".format( app.ui.fill_tool.boundary_mode_names[app.ui.fill_tool.boundary_mode] ) # # view menu # class ViewToggleCRTItem(ArtModePulldownMenuItem): label = " CRT filter" command = "toggle_crt" game_mode_allowed = True def should_dim(app): return app.fb.disable_crt def should_mark(ui): return ui.app.fb.crt class ViewToggleGridItem(ArtModePulldownMenuItem): label = " Grid" command = "toggle_grid_visibility" game_mode_allowed = True def should_mark(ui): return ui.app.grid.visible class ViewBGTextureItem(ArtModePulldownMenuItem): label = " Textured background" command = "toggle_bg_texture" always_active = True def should_mark(ui): return ui.app.show_bg_texture class ViewToggleZoomExtentsItem(ArtModePulldownMenuItem): label = " Zoom to Art extents" command = "toggle_zoom_extents" def should_mark(ui): return ui.active_art and ui.active_art.camera_zoomed_extents class ViewZoomInItem(ArtModePulldownMenuItem): label = "Zoom in" command = "camera_zoom_in_proportional" class ViewZoomOutItem(ArtModePulldownMenuItem): label = "Zoom out" command = "camera_zoom_out_proportional" class ViewSetZoomItem(ArtModePulldownMenuItem): label = "Set camera zoom…" command = "set_camera_zoom" class ViewToggleCameraTiltItem(ArtModePulldownMenuItem): label = " Camera tilt" command = "toggle_camera_tilt" always_active = True game_mode_allowed = True def should_mark(ui): return ui.app.camera.y_tilt != 0 class ViewSetOverlayImageItem(ArtModePulldownMenuItem): label = "Set overlay image…" command = "select_overlay_image" class ViewToggleOverlayImageItem(ArtModePulldownMenuItem): label = " Toggle overlay image" command = "toggle_overlay_image" def should_mark(ui): return ui.app.draw_overlay def should_dim(app): return app.overlay_renderable is None class ViewSetOverlayImageOpacityItem(ArtModePulldownMenuItem): label = "Set overlay image opacity…" command = "set_overlay_image_opacity" def should_dim(app): return app.overlay_renderable is None or not app.draw_overlay class ViewSetOverlayImageScalingItem(ArtModePulldownMenuItem): label = "blah" command = "set_overlay_image_scaling" def get_label(app): return "Overlay image scaling: {}".format( ["width", "height", "fill"][app.overlay_scale_type] ) def should_dim(app): return app.overlay_renderable is None or not app.draw_overlay # # art menu # class ArtOpenAllGameAssetsItem(ArtModePulldownMenuItem): label = "Open all Game Mode assets" command = "open_all_game_assets" def should_dim(app): return len(app.gw.objects) == 0 class ArtPreviousItem(ArtModePulldownMenuItem): label = "Previous Art" command = "previous_art" def should_dim(app): return len(app.art_loaded_for_edit) < 2 class ArtNextItem(ArtModePulldownMenuItem): label = "Next Art" command = "next_art" def should_dim(app): return len(app.art_loaded_for_edit) < 2 class ArtCropToSelectionItem(ArtModePulldownMenuItem): label = "Crop to selection" command = "crop_to_selection" def should_dim(app): return len(app.ui.select_tool.selected_tiles) == 0 class ArtResizeItem(ArtModePulldownMenuItem): label = "Resize…" command = "resize_art" class ArtFlipHorizontal(ArtModePulldownMenuItem): label = "Flip horizontally" command = "art_flip_horizontal" class ArtFlipVertical(ArtModePulldownMenuItem): label = "Flip vertically" command = "art_flip_vertical" class ArtToggleFlipAffectsXforms(ArtModePulldownMenuItem): label = " Flip affects xforms" command = "art_toggle_flip_affects_xforms" def should_mark(ui): return ui.flip_affects_xforms class ArtRunScriptItem(ArtModePulldownMenuItem): label = "Run Artscript…" command = "run_art_script" class ArtRunLastScriptItem(ArtModePulldownMenuItem): label = "Run last Artscript" command = "run_art_script_last" def should_dim(app): return app.last_art_script is None # # frame menu # class FramePreviousItem(ArtModePulldownMenuItem): label = "Previous frame" command = "previous_frame" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 class FrameNextItem(ArtModePulldownMenuItem): label = "Next frame" command = "next_frame" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 class FrameTogglePlaybackItem(ArtModePulldownMenuItem): label = "blah" command = "toggle_anim_playback" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 def get_label(app): if not app.ui.active_art: return "Start animation playback" animating = app.ui.active_art.renderables[0].animating return ["Start", "Stop"][animating] + " animation playback" class FrameToggleOnionItem(ArtModePulldownMenuItem): label = "blah" command = "toggle_onion_visibility" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 def get_label(app): l = "{} onion skin frames".format(["Show", "Hide"][app.onion_frames_visible]) return l class FrameCycleOnionFramesItem(ArtModePulldownMenuItem): label = "blah" command = "cycle_onion_frames" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 def get_label(app): return "Number of onion frames: {}".format(app.onion_show_frames) class FrameCycleOnionDisplayItem(ArtModePulldownMenuItem): label = "blah" command = "cycle_onion_ahead_behind" def should_dim(app): return not app.ui.active_art or app.ui.active_art.frames < 2 def get_label(app): if app.onion_show_frames_behind and app.onion_show_frames_ahead: display = "Next & Previous" elif app.onion_show_frames_behind: display = "Previous" else: display = "Next" return "Onion frames show: {}".format(display) class FrameAddFrameItem(ArtModePulldownMenuItem): label = "Add frame…" command = "add_frame" class FrameDuplicateFrameItem(ArtModePulldownMenuItem): label = "Duplicate this frame…" command = "duplicate_frame" class FrameChangeDelayItem(ArtModePulldownMenuItem): label = "Change this frame's hold time…" command = "change_frame_delay" class FrameChangeDelayAllItem(ArtModePulldownMenuItem): label = "Change all frames' hold times…" command = "change_frame_delay_all" class FrameChangeIndexItem(ArtModePulldownMenuItem): label = "Change this frame's index…" command = "change_frame_index" class FrameDeleteFrameItem(ArtModePulldownMenuItem): label = "Delete this frame" command = "delete_frame" def should_dim(app): # don't delete last frame return not app.ui.active_art or app.ui.active_art.frames < 2 # # layer menu # class LayerAddItem(ArtModePulldownMenuItem): label = "Add layer…" command = "add_layer" class LayerDuplicateItem(ArtModePulldownMenuItem): label = "Duplicate this layer…" command = "duplicate_layer" class LayerSetNameItem(ArtModePulldownMenuItem): label = "Change this layer's name…" command = "change_layer_name" class LayerSetZItem(ArtModePulldownMenuItem): label = "Change this layer's Z-depth…" command = "change_layer_z" class LayerToggleVisibleItem(ArtModePulldownMenuItem): label = "blah" command = "toggle_layer_visibility" def get_label(app): if not app.ui.active_art: return "Show this layer (Game Mode)" visible = app.ui.active_art.layers_visibility[app.ui.active_art.active_layer] return ["Show", "Hide"][visible] + " this layer (Game Mode)" class LayerDeleteItem(ArtModePulldownMenuItem): label = "Delete this layer" command = "delete_layer" def should_dim(app): return not app.ui.active_art or app.ui.active_art.layers < 2 class LayerSetInactiveVizItem(ArtModePulldownMenuItem): label = "blah" command = "cycle_inactive_layer_visibility" def should_dim(app): return not app.ui.active_art or app.ui.active_art.layers < 2 def get_label(app): l = "Inactive layers: " if app.inactive_layer_visibility == LAYER_VIS_FULL: return l + "Visible" elif app.inactive_layer_visibility == LAYER_VIS_DIM: return l + "Dim" elif app.inactive_layer_visibility == LAYER_VIS_NONE: return l + "Invisible" class LayerShowHiddenItem(ArtModePulldownMenuItem): label = "blah" command = "toggle_hidden_layers_visible" def get_label(app): l = "Art Mode-only layers: " l += ["Hidden", "Visible"][app.show_hidden_layers] return l class LayerPreviousItem(ArtModePulldownMenuItem): label = "Previous layer" command = "previous_layer" def should_dim(app): return not app.ui.active_art or app.ui.active_art.layers < 2 class LayerNextItem(ArtModePulldownMenuItem): label = "Next layer" command = "next_layer" def should_dim(app): return not app.ui.active_art or app.ui.active_art.layers < 2 # # char/color menu # class ChooseCharSetItem(ArtModePulldownMenuItem): label = "Choose character set…" command = "choose_charset" class ChoosePaletteItem(ArtModePulldownMenuItem): label = "Choose palette…" command = "choose_palette" class PaletteFromImageItem(ArtModePulldownMenuItem): label = "Palette from image…" command = "palette_from_file" always_active = True # # help menu # class HelpDocsItem(ArtModePulldownMenuItem): label = "Help (in browser)" command = "open_help_docs" always_active = True close_on_select = True game_mode_allowed = True class HelpGenerateDocsItem(ArtModePulldownMenuItem): label = "Generate documentation" command = "generate_docs" close_on_select = True def should_dim(app): return not app.pdoc_available class HelpWebsiteItem(ArtModePulldownMenuItem): label = "Playscii website" command = "open_website" always_active = True close_on_select = True # # menu data # class PulldownMenuData: "data for pulldown menus, eg File, Edit, etc; mainly a list of menu items" items = [] def should_mark_item(item, ui): "returns True if this item should be marked, subclasses have custom logic here" return False def get_items(app): """ returns a list of items generated from app state, used for dynamically-generated items """ return [] class FileMenuData(PulldownMenuData): items = [ FileNewItem, FileOpenItem, FileSaveItem, FileSaveAsItem, FileCloseItem, FileRevertItem, SeparatorItem, FileImportItem, FileExportItem, FileExportLastItem, SeparatorItem, FileQuitItem, ] class EditMenuData(PulldownMenuData): items = [ EditUndoItem, EditRedoItem, SeparatorItem, EditCutItem, EditCopyItem, EditPasteItem, EditDeleteItem, SeparatorItem, EditSelectAllItem, EditSelectNoneItem, EditSelectInvertItem, SeparatorItem, EditPreferences, ] class ToolMenuData(PulldownMenuData): items = [ ToolTogglePickerItem, ToolTogglePickerHoldItem, ToolSwapSelectedColors, ToolToggleArtToolbar, SeparatorItem, ToolPaintItem, ToolEraseItem, ToolRotateItem, ToolGrabItem, ToolTextItem, ToolSelectItem, ToolPasteItem, ToolFillItem, SeparatorItem, ToolIncreaseBrushSizeItem, ToolDecreaseBrushSizeItem, ToolToggleAffectsCharItem, ToolToggleAffectsFGItem, ToolToggleAffectsBGItem, ToolToggleAffectsXformItem, SeparatorItem, ToolSetFillBoundariesItem, ] # TODO: cycle char/color/xform items? # TODO: generate list from UI.tools instead of manually specified MenuItems def should_mark_item(item, ui): # if it's a tool setting toggle, use its own mark check function if item.__bases__[0] is ToolSettingsItem or hasattr(item, "should_mark"): return item.should_mark(ui) return item.label == " {}".format(ui.selected_tool.button_caption) class ViewMenuData(PulldownMenuData): items = [ ViewToggleCRTItem, ViewToggleGridItem, ViewBGTextureItem, SeparatorItem, ViewToggleZoomExtentsItem, ViewZoomInItem, ViewZoomOutItem, ViewSetZoomItem, ViewToggleCameraTiltItem, SeparatorItem, ViewSetOverlayImageItem, ViewToggleOverlayImageItem, ViewSetOverlayImageOpacityItem, ViewSetOverlayImageScalingItem, ] def should_mark_item(item, ui): if hasattr(item, "should_mark"): return item.should_mark(ui) return False class ArtMenuData(PulldownMenuData): items = [ ArtResizeItem, ArtCropToSelectionItem, ArtFlipHorizontal, ArtFlipVertical, ArtToggleFlipAffectsXforms, SeparatorItem, ArtRunScriptItem, ArtRunLastScriptItem, SeparatorItem, ArtOpenAllGameAssetsItem, SeparatorItem, ArtPreviousItem, ArtNextItem, SeparatorItem, ] def should_mark_item(item, ui): "show checkmark for active art" if hasattr(item, "should_mark"): return item.should_mark(ui) return ui.active_art and ui.active_art.filename == item.cb_arg def get_items(app): "turn each loaded art into a menu item" items = [] for art in app.art_loaded_for_edit: # class just being used to store data, no need to spawn it class TempMenuItemClass(ArtModePulldownMenuItem): pass item = TempMenuItemClass # leave spaces for mark item.label = " {}".format(art.filename) item.command = "art_switch_to" item.cb_arg = art.filename # order list by art's time loaded item.time_loaded = art.time_loaded items.append(item) items.sort(key=lambda item: item.time_loaded) return items class FrameMenuData(PulldownMenuData): items = [ FrameAddFrameItem, FrameDuplicateFrameItem, FrameChangeDelayItem, FrameChangeDelayAllItem, FrameChangeIndexItem, FrameDeleteFrameItem, SeparatorItem, FrameTogglePlaybackItem, FramePreviousItem, FrameNextItem, SeparatorItem, FrameToggleOnionItem, FrameCycleOnionFramesItem, FrameCycleOnionDisplayItem, ] class LayerMenuData(PulldownMenuData): items = [ LayerAddItem, LayerDuplicateItem, LayerSetNameItem, LayerSetZItem, LayerDeleteItem, SeparatorItem, LayerSetInactiveVizItem, LayerPreviousItem, LayerNextItem, SeparatorItem, LayerToggleVisibleItem, LayerShowHiddenItem, SeparatorItem, ] def should_mark_item(item, ui): "show checkmark for active art" if not ui.active_art: return False return ui.active_art.active_layer == item.cb_arg def get_items(app): "turn each layer into a menu item" items = [] if not app.ui.active_art: return items # first determine longest line to set width of items longest_line = 0 for layer_name in app.ui.active_art.layer_names: if len(layer_name) > longest_line: longest_line = len(layer_name) # check non-generated menu items too for item in LayerMenuData.items: if len(item.label) + 1 > longest_line: longest_line = len(item.label) + 1 # cap at max allowed line length longest_line = min(longest_line, 50) for i, layer_name in enumerate(app.ui.active_art.layer_names): class TempMenuItemClass(ArtModePulldownMenuItem): pass item = TempMenuItemClass # leave spaces for mark item.label = " {}".format(layer_name) if not app.ui.active_art.layers_visibility[i]: item.label += " (hidden)" # pad, put Z depth on far right item.label = item.label.ljust(longest_line) # trim to keep below a max length item.label = item.label[:longest_line] # spaces between layer name and z depth item.label += "z:{:.2f}".format(app.ui.active_art.layers_z[i]) # tell PulldownMenu's button creation process not to auto-pad item.no_pad = True item.command = "layer_switch_to" item.cb_arg = i items.append(item) return items class CharColorMenuData(PulldownMenuData): items = [ChooseCharSetItem, ChoosePaletteItem, SeparatorItem, PaletteFromImageItem] class HelpMenuData(PulldownMenuData): items = [HelpDocsItem, HelpGenerateDocsItem, HelpWebsiteItem]