#=============================================================================== # #=============================================================================== class AnimationEditor::AnimationSelector BORDER_THICKNESS = 4 QUIT_BUTTON_WIDTH = 80 QUIT_BUTTON_HEIGHT = 30 TYPE_BUTTONS_X = 2 TYPE_BUTTONS_Y = 62 TYPE_BUTTON_WIDTH = 100 TYPE_BUTTON_HEIGHT = 48 MOVES_LIST_X = TYPE_BUTTONS_X + TYPE_BUTTON_WIDTH + 4 MOVES_LIST_Y = TYPE_BUTTONS_Y + 4 MOVES_LIST_WIDTH = 200 MOVES_LIST_HEIGHT = 26 * UIControls::List::ROW_HEIGHT ANIMATIONS_LIST_X = MOVES_LIST_X + MOVES_LIST_WIDTH + 8 ANIMATIONS_LIST_Y = MOVES_LIST_Y ANIMATIONS_LIST_WIDTH = 300 ANIMATIONS_LIST_HEIGHT = MOVES_LIST_HEIGHT ACTION_BUTTON_WIDTH = 200 ACTION_BUTTON_HEIGHT = 48 ACTION_BUTTON_X = ANIMATIONS_LIST_X + ANIMATIONS_LIST_WIDTH + 4 ACTION_BUTTON_Y = TYPE_BUTTONS_Y + ((ANIMATIONS_LIST_HEIGHT - (ACTION_BUTTON_HEIGHT * 3)) / 2) + 4 # Pop-up window MESSAGE_BOX_WIDTH = AnimationEditor::WINDOW_WIDTH * 3 / 4 MESSAGE_BOX_HEIGHT = 160 MESSAGE_BOX_BUTTON_WIDTH = 150 MESSAGE_BOX_BUTTON_HEIGHT = 32 MESSAGE_BOX_SPACING = 16 def initialize generate_lists @viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) @viewport.z = 99999 @pop_up_viewport = Viewport.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) @pop_up_viewport.z = @viewport.z + 50 @screen_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @viewport) @pop_up_bg_bitmap = BitmapSprite.new(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, @pop_up_viewport) @pop_up_bg_bitmap.z = -100 @pop_up_bg_bitmap.visible = false draw_editor_background @animation_type = 0 # 0=move, 1=common @quit = false create_controls refresh end def dispose @screen_bitmap.dispose @pop_up_bg_bitmap.dispose @components.dispose @viewport.dispose @pop_up_viewport.dispose end LABEL_OFFSET_X = -4 LABEL_OFFSET_Y = -32 def create_controls @components = UIControls::ControlsContainer.new(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) # Quit button btn = UIControls::Button.new(QUIT_BUTTON_WIDTH, QUIT_BUTTON_HEIGHT, @viewport, _INTL("Quit")) btn.set_fixed_size @components.add_control_at(:quit, btn, 0, 0) # New button btn = UIControls::Button.new(QUIT_BUTTON_WIDTH, QUIT_BUTTON_HEIGHT, @viewport, _INTL("New")) btn.set_fixed_size @components.add_control_at(:new, btn, QUIT_BUTTON_WIDTH, 0) # Type label label = UIControls::Label.new(TYPE_BUTTON_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Anim types")) label.header = true @components.add_control_at(:type_label, label, TYPE_BUTTONS_X + LABEL_OFFSET_X + 4, TYPE_BUTTONS_Y + LABEL_OFFSET_Y + 4) # Animation type toggle buttons [[:moves, _INTL("Moves")], [:commons, _INTL("Common")]].each_with_index do |val, i| btn = UIControls::Button.new(TYPE_BUTTON_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, val[1]) btn.set_fixed_size @components.add_control_at(val[0], btn, TYPE_BUTTONS_X, TYPE_BUTTONS_Y + (i * TYPE_BUTTON_HEIGHT)) end # TODO: Add filter text box for :moves_list's contents. Applies the filter # upon every change to the text box's value. Perhaps it should only do # so after 0.5 seconds of non-typing. What exactly should the filter # be applied to? Animation's name, move's name (if there is one), what # else? Flags? # Moves list label label = UIControls::Label.new(MOVES_LIST_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Moves")) label.header = true @components.add_control_at(:moves_label, label, MOVES_LIST_X + LABEL_OFFSET_X, MOVES_LIST_Y + LABEL_OFFSET_Y) # Moves list list = UIControls::List.new(MOVES_LIST_WIDTH, MOVES_LIST_HEIGHT, @viewport, []) @components.add_control_at(:moves_list, list, MOVES_LIST_X, MOVES_LIST_Y) # Animations list label label = UIControls::Label.new(ANIMATIONS_LIST_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Animations")) label.header = true @components.add_control_at(:animations_label, label, ANIMATIONS_LIST_X + LABEL_OFFSET_X, ANIMATIONS_LIST_Y + LABEL_OFFSET_Y) # Animations list list = UIControls::List.new(ANIMATIONS_LIST_WIDTH, ANIMATIONS_LIST_HEIGHT, @viewport, []) @components.add_control_at(:animations_list, list, ANIMATIONS_LIST_X, ANIMATIONS_LIST_Y) # Edit, Copy and Delete buttons [[:edit, _INTL("Edit animation")], [:copy, _INTL("Copy animation")], [:delete, _INTL("Delete animation")]].each_with_index do |val, i| btn = UIControls::Button.new(ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, @viewport, val[1]) btn.set_fixed_size @components.add_control_at(val[0], btn, ACTION_BUTTON_X, ACTION_BUTTON_Y + (i * ACTION_BUTTON_HEIGHT)) end end def draw_editor_background # Fill the whole screen with white @screen_bitmap.bitmap.fill_rect(0, 0, AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT, Color.white) # Outlines around lists areas = [ [MOVES_LIST_X, MOVES_LIST_Y, MOVES_LIST_WIDTH, MOVES_LIST_HEIGHT], [ANIMATIONS_LIST_X, ANIMATIONS_LIST_Y, ANIMATIONS_LIST_WIDTH, ANIMATIONS_LIST_HEIGHT] ] areas.each do |area| @screen_bitmap.bitmap.outline_rect(area[0] - 2, area[1] - 2, area[2] + 4, area[3] + 4, Color.black) end end #----------------------------------------------------------------------------- def create_pop_up_window(width, height) ret = BitmapSprite.new(width + (BORDER_THICKNESS * 2), height + (BORDER_THICKNESS * 2), @pop_up_viewport) ret.x = (AnimationEditor::WINDOW_WIDTH - ret.width) / 2 ret.y = (AnimationEditor::WINDOW_HEIGHT - ret.height) / 2 ret.z = -1 ret.bitmap.font.color = Color.black ret.bitmap.font.size = 18 # Draw pop-up box border ret.bitmap.border_rect(BORDER_THICKNESS, BORDER_THICKNESS, width, height, BORDER_THICKNESS, Color.white, Color.black) # Fill pop-up box with white ret.bitmap.fill_rect(BORDER_THICKNESS, BORDER_THICKNESS, width, height, Color.white) return ret end #----------------------------------------------------------------------------- def message(text, *options) @pop_up_bg_bitmap.visible = true msg_bitmap = create_pop_up_window(MESSAGE_BOX_WIDTH, MESSAGE_BOX_HEIGHT) # Draw text text_size = msg_bitmap.bitmap.text_size(text) msg_bitmap.bitmap.draw_text(0, (msg_bitmap.height / 2) - MESSAGE_BOX_BUTTON_HEIGHT, msg_bitmap.width, text_size.height, text, 1) # Create buttons buttons = [] options.each_with_index do |option, i| btn = UIControls::Button.new(MESSAGE_BOX_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, @pop_up_viewport, option[1]) btn.x = msg_bitmap.x + (msg_bitmap.width - (MESSAGE_BOX_BUTTON_WIDTH * options.length)) / 2 btn.x += MESSAGE_BOX_BUTTON_WIDTH * i btn.y = msg_bitmap.y + msg_bitmap.height - MESSAGE_BOX_BUTTON_HEIGHT - MESSAGE_BOX_SPACING btn.set_fixed_size btn.set_interactive_rects buttons.push([option[0], btn]) end # Interaction loop ret = nil captured = nil loop do Graphics.update Input.update if captured captured.update captured = nil if !captured.busy? else buttons.each do |btn| btn[1].update captured = btn[1] if btn[1].busy? end end buttons.each do |btn| next if !btn[1].changed? ret = btn[0] break end ret = :cancel if Input.trigger?(Input::BACK) break if ret buttons.each { |btn| btn[1].repaint } end # Dispose and return buttons.each { |btn| btn[1].dispose } buttons.clear msg_bitmap.dispose @pop_up_bg_bitmap.visible = false return ret end def confirm_message(text) return message(text, [:yes, _INTL("Yes")], [:no, _INTL("No")]) == :yes end #----------------------------------------------------------------------------- def generate_lists @move_list = [] @common_list = [] @move_animations = {} @common_animations = {} GameData::Animation.keys.each do |id| anim = GameData::Animation.get(id) name = "" name += _INTL("[Foe]") + " " if anim.opposing_animation? name += "[#{anim.version}]" + " " if anim.version > 0 name += (anim.name || anim.move) if anim.move_animation? move_name = GameData::Move.try_get(anim.move)&.name || anim.move @move_list.push([anim.move, move_name]) if !@move_animations[anim.move] @move_animations[anim.move] ||= [] @move_animations[anim.move].push([id, name]) elsif anim.common_animation? @common_list.push([anim.move, anim.move]) if !@common_animations[anim.move] @common_animations[anim.move] ||= [] @common_animations[anim.move].push([id, name]) end end @move_list.sort! @common_list.sort! @move_animations.values.each do |val| val.sort! { |a, b| a[1] <=> b[1] } end @common_animations.values.each do |val| val.sort! { |a, b| a[1] <=> b[1] } end end def selected_move_animations val = @components.get_control(:moves_list).value return [] if !val return @move_animations[val] if @animation_type == 0 return @common_animations[val] if @animation_type == 1 return [] end def selected_animation_id return @components.get_control(:animations_list).value end #----------------------------------------------------------------------------- def refresh # Put the correct list into the moves list case @animation_type when 0 @components.get_control(:moves).disable @components.get_control(:commons).enable @components.get_control(:moves_list).values = @move_list @components.get_control(:moves_label).text = _INTL("Moves") when 1 @components.get_control(:moves).enable @components.get_control(:commons).disable @components.get_control(:moves_list).values = @common_list @components.get_control(:moves_label).text = _INTL("Common animations") end # Put the correct list into the animations list @components.get_control(:animations_list).values = selected_move_animations # Enable/disable buttons depending on what is selected if @components.get_control(:animations_list).value @components.get_control(:edit).enable @components.get_control(:copy).enable @components.get_control(:delete).enable else @components.get_control(:edit).disable @components.get_control(:copy).disable @components.get_control(:delete).disable end end #----------------------------------------------------------------------------- def apply_button_press(button) case button when :quit @quit = true return # Don't need to refresh the screen when :new new_anim = GameData::Animation.new_hash(@animation_type, @components.get_control(:moves_list).value) new_id = GameData::Animation.keys.max + 1 screen = AnimationEditor.new(new_id, new_anim) screen.run generate_lists when :moves @animation_type = 0 @components.get_control(:moves_list).selected = -1 @components.get_control(:animations_list).selected = -1 when :commons @animation_type = 1 @components.get_control(:moves_list).selected = -1 @components.get_control(:animations_list).selected = -1 when :edit anim_id = selected_animation_id if anim_id screen = AnimationEditor.new(anim_id, GameData::Animation.get(anim_id).clone_as_hash) screen.run generate_lists end when :copy anim_id = selected_animation_id if anim_id new_anim = GameData::Animation.get(anim_id).clone_as_hash new_anim[:name] += " " + _INTL("(copy)") if !nil_or_empty?(new_anim[:name]) new_id = GameData::Animation.keys.max + 1 screen = AnimationEditor.new(new_id, new_anim) screen.run generate_lists end when :delete anim_id = selected_animation_id if anim_id && confirm_message(_INTL("Are you sure you want to delete this animation?")) pbs_path = GameData::Animation.get(anim_id).pbs_path GameData::Animation::DATA.delete(anim_id) if GameData::Animation::DATA.any? { |_key, anim| anim.pbs_path == pbs_path } Compiler.write_battle_animation_file(pbs_path) elsif FileTest.exist?("PBS/Animations/" + pbs_path + ".txt") File.delete("PBS/Animations/" + pbs_path + ".txt") end generate_lists end end refresh end def update @components.update if @components.changed? @components.values.each_pair do |property, value| apply_button_press(property) end @components.clear_changed end end def run Input.text_input = false loop do Graphics.update Input.update update break if !@components.busy? && @quit end dispose end end #=============================================================================== # Add to Debug menu. #=============================================================================== MenuHandlers.add(:debug_menu, :use_pc, { "name" => _INTL("New Animation Editor"), "parent" => :main, "description" => _INTL("Open the new animation editor."), "effect" => proc { Graphics.resize_screen(AnimationEditor::WINDOW_WIDTH, AnimationEditor::WINDOW_HEIGHT) pbSetResizeFactor(1) screen = AnimationEditor::AnimationSelector.new screen.run Graphics.resize_screen(Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) pbSetResizeFactor($PokemonSystem.screensize) $game_map&.autoplay } })