Added new/shift buttons to Anim Editor timeline

This commit is contained in:
Maruno17
2024-02-15 20:15:03 +00:00
parent 4455c093b8
commit 67acf46859
6 changed files with 249 additions and 39 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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
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

View File

@@ -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
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

View File

@@ -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]
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

View File

@@ -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