From 67acf46859c48cd649f86291881d2414a8cf291a Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Thu, 15 Feb 2024 20:15:03 +0000 Subject: [PATCH] Added new/shift buttons to Anim Editor timeline --- .../Control elements/007b_BitmapButton.rb | 30 +++++ .../902_Anim GameData/001_Animation.rb | 2 +- .../904_Anim Editor/001_AnimationEditor.rb | 80 ++++++++----- .../904_Anim Editor/901_ParticleDataHelper.rb | 49 +++++++- .../Anim Editor elements/001_Canvas.rb | 22 +++- .../Anim Editor elements/003_ParticleList.rb | 105 +++++++++++++++++- 6 files changed, 249 insertions(+), 39 deletions(-) create mode 100644 Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb diff --git a/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb b/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb new file mode 100644 index 000000000..7b3b258bf --- /dev/null +++ b/Data/Scripts/801_UI controls/Control elements/007b_BitmapButton.rb @@ -0,0 +1,30 @@ +#=============================================================================== +# +#=============================================================================== +class UIControls::BitmapButton < UIControls::Button + BUTTON_PADDING = 4 + + def initialize(x, y, viewport, button_bitmap) + super(button_bitmap.width + (BUTTON_PADDING * 2), button_bitmap.height + (BUTTON_PADDING * 2), viewport) + self.x = x + self.y = y + @button_bitmap = button_bitmap + end + + def set_interactive_rects + @interactions&.clear + @button_rect = Rect.new(0, 0, width, height) + @interactions = { + :button => @button_rect + } + end + + #----------------------------------------------------------------------------- + + def refresh + super + # Draw button bitmap + self.bitmap.blt(BUTTON_PADDING, BUTTON_PADDING, @button_bitmap, + Rect.new(0, 0, @button_bitmap.width, @button_bitmap.height)) + end +end diff --git a/Data/Scripts/902_Anim GameData/001_Animation.rb b/Data/Scripts/902_Anim GameData/001_Animation.rb index 29972842b..5c32f614f 100644 --- a/Data/Scripts/902_Anim GameData/001_Animation.rb +++ b/Data/Scripts/902_Anim GameData/001_Animation.rb @@ -40,7 +40,7 @@ module GameData "EaseOut" => :ease_out, "EaseBoth" => :ease_both } - USER_AND_TARGET_SEPARATION = [200, -200, -200] # x, y, z (from user to target) + USER_AND_TARGET_SEPARATION = [200, -200, -100] # x, y, z (from user to target) # Properties that apply to the animation in general, not to individual # particles. They don't change during the animation. diff --git a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb index 7464fa212..386e9743d 100644 --- a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb +++ b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb @@ -206,8 +206,8 @@ class AnimationEditor def set_commands_pane_contents commands_pane = @components[:commands_pane] commands_pane.add_header_label(:header, _INTL("Edit particle at keyframe")) - commands_pane.add_labelled_number_text_box(:x, _INTL("X"), -200, 200, 0) - commands_pane.add_labelled_number_text_box(:y, _INTL("Y"), -200, 200, 0) + commands_pane.add_labelled_number_text_box(:x, _INTL("X"), -999, 999, 0) + commands_pane.add_labelled_number_text_box(:y, _INTL("Y"), -999, 999, 0) commands_pane.add_labelled_number_slider(:z, _INTL("Priority"), -50, 50, 0) # TODO: If the graphic is user's sprite/target's sprite, make :frame instead # a choice of front/back/same as the main sprite/opposite of the main @@ -257,7 +257,7 @@ class AnimationEditor particle_pane.get_control(:name).set_blacklist("User", "Target", "SE") particle_pane.add_labelled_label(:graphic_name, _INTL("Graphic"), "") particle_pane.add_labelled_button(:graphic, "", _INTL("Change")) - particle_pane.add_labelled_dropdown_list(:focus, _INTL("Focus"), {}, :user) + particle_pane.add_labelled_dropdown_list(:focus, _INTL("Focus"), {}, :undefined) # FlipIfFoe # RotateIfFoe # Delete button (if not "User"/"Target"/"SE") @@ -430,6 +430,8 @@ class AnimationEditor @pop_up_bg_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.new(0, 0, 0, 128)) end + #----------------------------------------------------------------------------- + def refresh_component_visibility(component_sym) component = @components[component_sym] # Panes are all mutually exclusive @@ -478,26 +480,6 @@ class AnimationEditor # TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated, # which should be indicated somehow in ctrl[1]. end - # Set an appropriate range for the X and Y properties depending on the - # particle's focus - case @anim[:particles][particle_index][:focus] - when :foreground, :midground, :background # Cover the whole screen - component.get_control(:x).min_value = -128 - component.get_control(:x).max_value = CANVAS_WIDTH + 128 - component.get_control(:y).min_value = -128 - component.get_control(:y).max_value = CANVAS_HEIGHT + 128 - when :user, :target, :user_side_foreground, :user_side_background, - :target_side_foreground, :target_side_background # Around the focus - component.get_control(:x).min_value = -CANVAS_WIDTH - component.get_control(:x).max_value = CANVAS_WIDTH - component.get_control(:y).min_value = -CANVAS_HEIGHT - component.get_control(:y).max_value = CANVAS_HEIGHT - when :user_and_target # Covers both foci - component.get_control(:x).min_value = -CANVAS_WIDTH - component.get_control(:x).max_value = GameData::Animation::USER_AND_TARGET_SEPARATION[0] + CANVAS_WIDTH - component.get_control(:y).min_value = GameData::Animation::USER_AND_TARGET_SEPARATION[1] - CANVAS_HEIGHT - component.get_control(:y).max_value = CANVAS_HEIGHT - end # Set an appropriate range for the priority (z) property depending on the # particle's focus case @anim[:particles][particle_index][:focus] @@ -598,6 +580,20 @@ class AnimationEditor :target_side_background => _INTL("Behind target's side") } end + when :particle_list + # Disable the "move particle up/down" buttons if the selected particle + # can't move that way (or there is no selected particle) + cur_index = particle_index + if cur_index < 1 + component.get_control(:move_particle_up).disable + else + component.get_control(:move_particle_up).enable + end + if cur_index < 0 || cur_index >= @anim[:particles].length - 2 + component.get_control(:move_particle_down).disable + else + component.get_control(:move_particle_down).enable + end when :animation_properties refresh_move_property_options case @anim[:type] @@ -721,8 +717,33 @@ class AnimationEditor when :keyframe_pane # TODO: Stuff here once I decide what controls to add. when :particle_list -# refresh if keyframe != old_keyframe || particle_index != old_particle_index - # TODO: Lots of stuff here when buttons are added to it. + case property + when :add_particle + new_idx = particle_index + if new_idx >= 0 + new_idx += 1 + new_idx = @anim[:particles].length - 1 if new_idx == 0 || new_idx >= @anim[:particles].length + end + AnimationEditor::ParticleDataHelper.add_particle(@anim[:particles], new_idx) + @components[:particle_list].set_particles(@anim[:particles]) + @components[:particle_list].particle_index = (new_idx >= 0) ? new_idx : @anim[:particles].length - 2 + @components[:particle_list].keyframe = -1 + refresh + when :move_particle_up + idx1 = particle_index + idx2 = idx1 - 1 + AnimationEditor::ParticleDataHelper.swap_particles(@anim[:particles], idx1, idx2) + @components[:particle_list].set_particles(@anim[:particles]) + @components[:particle_list].particle_index = idx2 + refresh + when :move_particle_down + idx1 = particle_index + idx2 = idx1 + 1 + AnimationEditor::ParticleDataHelper.swap_particles(@anim[:particles], idx1, idx2) + @components[:particle_list].set_particles(@anim[:particles]) + @components[:particle_list].particle_index = idx2 + refresh + end when :animation_properties # TODO: Will changes here need to refresh any other components (e.g. side # panes)? Probably. @@ -769,14 +790,15 @@ class AnimationEditor if component.respond_to?("values") # TODO: Make undo/redo snapshot. values = component.values - values.each_pair do |property, value| - apply_changed_value(sym, property, value) + if values + values.each_pair do |property, value| + apply_changed_value(sym, property, value) + end end end component.clear_changed end - # TODO: Call repaint only if component responds to it? Canvas won't. - component.repaint if sym == :particle_list || sym == :menu_bar + component.repaint if [:particle_list, :menu_bar].include?(sym) if @captured @captured = nil if !component.busy? break diff --git a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb index 3aa2bb22b..94ee451dd 100644 --- a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb +++ b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb @@ -190,17 +190,37 @@ module AnimationEditor::ParticleDataHelper end interps[frame] = interp if interp != :none set_points[frame] = value + # For visibility only, set the keyframe with the first command (of any kind) + # to be visible, unless the command being added overwrites it. Also figure + # out the first keyframe that has a command, and the first keyframe that has + # a non-visibility command (used below). + if property == :visible + first_cmd = (["User", "Target", "SE"].include?(particle[:name])) ? 0 : -1 + first_non_visible_cmd = -1 + particle.each_pair do |prop, value| + next if !value.is_a?(Array) || value.length == 0 + next if prop == property && value[0][0] == frame + first_cmd = value[0][0] if first_cmd < 0 || first_cmd > value[0][0] + next if prop == :visible + first_non_visible_cmd = value[0][0] if first_non_visible_cmd < 0 || first_non_visible_cmd > value[0][0] + end + set_points[first_cmd] = true if first_cmd >= 0 && set_points[first_cmd].nil? + end # Convert points and interps back into particle[property] ret = [] if !GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.include?(property) raise _INTL("Couldn't get default value for property {1}.", property) end val = GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES[property] - val = true if property == :visible && ["User", "Target", "SE"].include?(particle[:name]) length = [set_points.length, end_points.length].max length.times do |i| - if !set_points[i].nil? && set_points[i] != val - ret.push([i, 0, set_points[i]]) + if !set_points[i].nil? + if property == :visible && first_cmd >= 0 && i == first_cmd && + first_non_visible_cmd >= 0 && i == first_non_visible_cmd + ret.push([i, 0, set_points[i]]) if !set_points[i] + elsif set_points[i] != val + ret.push([i, 0, set_points[i]]) + end val = set_points[i] end if interps[i] && interps[i] != :none @@ -296,4 +316,27 @@ module AnimationEditor::ParticleDataHelper particle.delete(:se) if particle[:se].empty? end end + + #----------------------------------------------------------------------------- + + # Creates a new particle and inserts it at index. If there is a particle above + # the new one, the new particle will inherit its focus; otherwise it gets a + # default focus of :foreground. + def add_particle(particles, index) + new_particle = { + :name => _INTL("New particle"), + :graphic => GameData::Animation::PARTICLE_DEFAULT_VALUES[:graphic], + :focus => GameData::Animation::PARTICLE_DEFAULT_VALUES[:focus] + } + if index > 0 && index <= particles.length - 1 + old_particle = particles[index - 1] + new_particle[:focus] = old_particle[:focus] + end + index = particles.length - 1 if index < 0 + particles.insert(index, new_particle) + end + + def swap_particles(particles, index1, index2) + particles[index1], particles[index2] = particles[index2], particles[index1] + end end diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb index 78854821e..abec007c1 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb @@ -299,7 +299,7 @@ class AnimationEditor::Canvas < Sprite spr.x = user_pos[0] + ((values[:x].to_f / distance[0]) * (target_pos[0] - user_pos[0])).to_i spr.y = user_pos[1] + ((values[:y].to_f / distance[1]) * (target_pos[1] - user_pos[1])).to_i when :user_side_foreground, :user_side_background - base_coords = Battle::Scene.pbBattlerPosition(target_idx) + base_coords = Battle::Scene.pbBattlerPosition(user_index) spr.x += base_coords[0] spr.y += base_coords[1] when :target_side_foreground, :target_side_background @@ -350,9 +350,12 @@ class AnimationEditor::Canvas < Sprite spr.y += spr.bitmap.height / 2 else spr.bitmap = RPG::Cache.load_bitmap("Graphics/Battle animations/", particle[:graphic]) - # TODO: Set the oy to spr.bitmap.height if particle[:graphic] has - # something special in it (don't know what yet). - if spr.bitmap.width > spr.bitmap.height * 2 + if [:foreground, :midground, :background].include?(particle[:focus]) && + spr.bitmap.width == AnimationEditor::CANVAS_WIDTH && + spr.bitmap.height >= AnimationEditor::CANVAS_HEIGHT - @message_bar_sprite.y + spr.ox = 0 + spr.oy = 0 + elsif spr.bitmap.width > spr.bitmap.height * 2 spr.src_rect.set(values[:frame] * spr.bitmap.height, 0, spr.bitmap.height, spr.bitmap.height) spr.ox = spr.bitmap.height / 2 spr.oy = spr.bitmap.height / 2 @@ -361,6 +364,9 @@ class AnimationEditor::Canvas < Sprite spr.ox = spr.bitmap.width / 2 spr.oy = spr.bitmap.height / 2 end + if particle[:graphic][/\[\s*bottom\s*\]\s*$/i] # [bottom] at end of filename + spr.oy = spr.bitmap.height + end end # Set z (priority) spr.z = values[:z] @@ -379,7 +385,13 @@ class AnimationEditor::Canvas < Sprite user_pos = 1000 + ((100 * ((user_index / 2) + 1)) * (user_index.even? ? 1 : -1)) target_pos = 1000 + ((100 * ((target_idx / 2) + 1)) * (target_idx.even? ? 1 : -1)) distance = GameData::Animation::USER_AND_TARGET_SEPARATION[2] - spr.z = user_pos + ((values[:z].to_f / distance) * (target_pos - user_pos)).to_i + if values[:z] >= 0 + spr.z += user_pos + elsif values[:z] <= distance + spr.z += target_pos + else + spr.z = user_pos + ((values[:z].to_f / distance) * (target_pos - user_pos)).to_i + end when :user_side_foreground, :target_side_foreground this_idx = (particle[:focus] == :user_side_foreground) ? user_index : target_idx spr.z += 1000 diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb index 9377d4d58..a24a29bed 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb @@ -1,5 +1,6 @@ #=============================================================================== -# +# TODO: The Add/Up/Down buttons don't get captured, and don't prevent anything +# else in this control highlighting when hovered over, and vice versa. #=============================================================================== class AnimationEditor::ParticleList < UIControls::BaseControl VIEWPORT_SPACING = 1 @@ -33,6 +34,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl SE_CONTROL_BG = Color.gray attr_reader :keyframe # The selected keyframe + attr_reader :values def initialize(x, y, width, height, viewport) super(width, height, viewport) @@ -75,6 +77,23 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @position_sprite = BitmapSprite.new(3, height - UIControls::Scrollbar::SLIDER_WIDTH - VIEWPORT_SPACING, @position_viewport) @position_sprite.ox = @position_sprite.width / 2 @position_sprite.bitmap.fill_rect(0, 0, @position_sprite.bitmap.width, @position_sprite.bitmap.height, Color.red) + # Selected particle line sprite + @particle_line_sprite = BitmapSprite.new(@position_viewport.rect.width, 3, @commands_viewport) + @particle_line_sprite.z = -10 + @particle_line_sprite.oy = @particle_line_sprite.height / 2 + @particle_line_sprite.bitmap.fill_rect(0, 0, @particle_line_sprite.bitmap.width, @particle_line_sprite.bitmap.height, Color.red) + # Buttons and button bitmaps + initialze_button_bitmaps + @controls = [] + add_particle_button = UIControls::BitmapButton.new(x + 1, y + 1, viewport, @add_button_bitmap) + add_particle_button.set_interactive_rects + @controls.push([:add_particle, add_particle_button]) + up_particle_button = UIControls::BitmapButton.new(x + 22, y + 1, viewport, @up_button_bitmap) + up_particle_button.set_interactive_rects + @controls.push([:move_particle_up, up_particle_button]) + down_particle_button = UIControls::BitmapButton.new(x + 43, y + 1, viewport, @down_button_bitmap) + down_particle_button.set_interactive_rects + @controls.push([:move_particle_down, down_particle_button]) # List sprites and commands sprites @list_sprites = [] @commands_bg_sprites = [] @@ -94,6 +113,22 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @commands = {} end + def initialze_button_bitmaps + @add_button_bitmap = Bitmap.new(12, 12) + @add_button_bitmap.fill_rect(1, 5, 10, 2, TEXT_COLOR) + @add_button_bitmap.fill_rect(5, 1, 2, 10, TEXT_COLOR) + @up_button_bitmap = Bitmap.new(12, 12) + 5.times do |i| + @up_button_bitmap.fill_rect(1 + i, 7 - i, 1, (i == 0) ? 2 : 3, TEXT_COLOR) + @up_button_bitmap.fill_rect(10 - i, 7 - i, 1, (i == 0) ? 2 : 3, TEXT_COLOR) + end + @down_button_bitmap = Bitmap.new(12, 12) + 5.times do |i| + @down_button_bitmap.fill_rect(1 + i, 2 + i + (i == 0 ? 1 : 0), 1, (i == 0) ? 2 : 3, TEXT_COLOR) + @down_button_bitmap.fill_rect(10 - i, 2 + i + (i == 0 ? 1 : 0), 1, (i == 0) ? 2 : 3, TEXT_COLOR) + end + end + def draw_control_background self.bitmap.clear # Separator lines @@ -117,6 +152,12 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @time_scrollbar.dispose @timeline_sprite.dispose @position_sprite.dispose + @particle_line_sprite.dispose + @controls.each { |c| c[1].dispose } + @controls.clear + @add_button_bitmap.dispose + @up_button_bitmap.dispose + @down_button_bitmap.dispose dispose_listed_sprites @list_viewport.dispose @commands_bg_viewport.dispose @@ -133,6 +174,19 @@ class AnimationEditor::ParticleList < UIControls::BaseControl return (ret.is_a?(Array)) ? ret[0] : ret end + def particle_index=(val) + old_index = @row_index + @row_index = @particle_list.index { |row| (row.is_a?(Array) && row[0] == val) || + (!row.is_a?(Array) && row == val) } + invalidate if @row_index != old_index + end + + def keyframe=(val) + return if @keyframe == val + @keyframe = val + invalidate + end + def top_pos=(val) old_val = @top_pos total_height = (@particle_list.length * ROW_HEIGHT) + 1 @@ -146,6 +200,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @commands_bg_viewport.oy = @top_pos @commands_viewport.oy = @top_pos if @top_pos != old_val + refresh_particle_line invalidate_rows @old_top_pos = old_val end @@ -247,6 +302,34 @@ class AnimationEditor::ParticleList < UIControls::BaseControl @invalid_commands = false end + def busy? + return true if @controls.any? { |c| c[1].busy? } + return super + end + + def changed? + return @changed + end + + def set_changed + @changed = true + @values = {} + end + + def clear_changed + super + @values = nil + end + + def get_control(id) + ret = nil + @controls.each do |c| + ret = c[1] if c[0] == id + break if ret + end + return ret + end + #----------------------------------------------------------------------------- def calculate_duration @@ -383,6 +466,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl :flip => _INTL("Flip"), :x => _INTL("X"), :y => _INTL("Y"), + :z => _INTL("Priority"), :zoom_x => _INTL("Zoom X"), :zoom_y => _INTL("Zoom Y"), :angle => _INTL("Angle"), @@ -394,6 +478,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl def repaint @list_scrollbar.repaint if @list_scrollbar.invalid? @time_scrollbar.repaint if @time_scrollbar.invalid? + @controls.each { |c| c[1].repaint if c[1].invalid? } super if invalid? end @@ -437,6 +522,13 @@ class AnimationEditor::ParticleList < UIControls::BaseControl end end + def refresh_particle_line + @particle_line_sprite.visible = (particle_index >= 0) + if particle_index >= 0 + @particle_line_sprite.y = ((@row_index + 0.5) * ROW_HEIGHT).to_i + end + end + # TODO: Add indicator that this is selected (if so). Some kind of arrow on the # left, or a red horizontal line (like the keyframe's vertical line), or # fill_rect with colour instead of outline_rect? @@ -576,6 +668,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl def refresh @old_top_pos = nil if @invalid + @controls.each { |c| c[1].refresh } draw_area_highlight refresh_timeline if @invalid || @invalid_time each_visible_particle do |i| @@ -717,12 +810,22 @@ class AnimationEditor::ParticleList < UIControls::BaseControl return if !self.visible @list_scrollbar.update @time_scrollbar.update + @controls.each { |c| c[1].update } super # Refresh sprites if a scrollbar has been moved self.top_pos = @list_scrollbar.position self.left_pos = @time_scrollbar.position # Update the current keyframe line's position refresh_position_line + # Update the selected particle line's position + refresh_particle_line + # Add/move particle buttons + @controls.each do |c| + next if !c[1].changed? + set_changed + @values[c[0]] = true + c[1].clear_changed + end if Input.release?(Input::MOUSERIGHT) on_right_mouse_release