Anim Editor: added interpolation editing

This commit is contained in:
Maruno17
2024-03-16 00:08:00 +00:00
parent ae32d59eb9
commit 054d7820e4
4 changed files with 385 additions and 124 deletions

View File

@@ -41,8 +41,8 @@ module GameData
"None" => :none,
"Linear" => :linear,
"EaseIn" => :ease_in,
"EaseOut" => :ease_out,
"EaseBoth" => :ease_both
"EaseBoth" => :ease_both,
"EaseOut" => :ease_out
}
USER_AND_TARGET_SEPARATION = [200, -200, -100] # x, y, z (from user to target)
@@ -66,7 +66,7 @@ module GameData
# change during the animation.
# TODO: If more "SetXYZ"/"MoveXYZ" properties are added, ensure the "SetXYZ"
# ones are given a duration of 0 in def validate_compiled_animation.
# Also add display names to def property_display_name.
# Also add display names to def self.property_display_name.
SUB_SCHEMA = {
# These properties cannot be changed partway through the animation.
# NOTE: "Name" isn't a property here, because the particle's name comes
@@ -162,6 +162,30 @@ module GameData
:target_cry => nil
}
def self.property_display_name(property)
return {
:frame => _INTL("Frame"),
:blending => _INTL("Blending"),
: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"),
:visible => _INTL("Visible"),
:opacity => _INTL("Opacity")
}[property] || property.capitalize
end
def self.property_can_interpolate?(property)
return false if !property
SUB_SCHEMA.each_value do |prop|
return true if prop[0] == property && prop[5] && prop[5] == INTERPOLATION_TYPES
end
return false
end
@@cmd_to_pbs_name = nil # Used for writing animation PBS files
extend ClassMethodsIDNumbers

View File

@@ -296,6 +296,9 @@ class AnimationEditor
# Move particle up/down the list?
end
# TODO: :keyframe_pane is currently inaccessible (intentionally). If it will
# have its own commands and should be accessible again, change def
# on_mouse_release in ParticleList.
def set_keyframe_pane_contents
keyframe_pane = @components[:keyframe_pane]
keyframe_pane.add_header_label(:header, _INTL("Edit keyframe"))
@@ -810,6 +813,24 @@ class AnimationEditor
@components[:particle_list].set_particles(@anim[:particles])
@components[:particle_list].particle_index = idx2
refresh
when :cycle_interpolation
# value is [particle index, property, keyframe]
# Get current interpolation type
interp_type = nil
@anim[:particles][value[0]][value[1]].each do |cmd|
next if cmd[0] != value[2]
interp_type = cmd[3] if !interp_type
end
interp_type ||= :none
# Get the interpolation type to change to
interps = GameData::Animation::INTERPOLATION_TYPES.values
idx = (interps.index(interp_type) + 1) % interps.length
interp_type = interps[idx]
# Set the new interpolation type
AnimationEditor::ParticleDataHelper.set_interpolation(@anim[:particles][value[0]], value[1], value[2], interp_type)
@components[:particle_list].change_particle_commands(value[0])
refresh_component(:commands_pane)
refresh_component(:canvas)
end
when :animation_properties
# TODO: Will changes here need to refresh any other components (e.g. side

View File

@@ -16,7 +16,7 @@ module AnimationEditor::ParticleDataHelper
return ret
end
def get_keyframe_particle_value(particle, frame, property)
def get_keyframe_particle_value(particle, property, frame)
if !GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.include?(property)
raise _INTL("Couldn't get default value for property {1} for particle {2}.",
property, particle[:name])
@@ -81,7 +81,7 @@ module AnimationEditor::ParticleDataHelper
def get_all_keyframe_particle_values(particle, frame)
ret = {}
GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.each_pair do |prop, default|
ret[prop] = get_keyframe_particle_value(particle, frame, prop)
ret[prop] = get_keyframe_particle_value(particle, prop, frame)
end
return ret
end
@@ -151,7 +151,7 @@ module AnimationEditor::ParticleDataHelper
# 0 - SetXYZ
# [+/- duration, interpolation type] --- MoveXYZ (duration's sign is whether
# it makes the value higher or lower)
def get_particle_property_commands_timeline(particle, commands, property)
def get_particle_property_commands_timeline(particle, property, commands)
return nil if !commands || commands.length == 0
if particle[:name] == "SE"
ret = []
@@ -188,6 +188,93 @@ module AnimationEditor::ParticleDataHelper
end
def add_command(particle, property, frame, value)
# Return a new set of commands if there isn't one
if !particle[property] || particle[property].empty?
return [[frame, 0, value]]
end
# Find all relevant commands
set_now = nil
move_ending_now = nil
move_overlapping_now = nil
particle[property].each do |cmd|
if cmd[1] == 0
set_now = cmd if cmd[0] == frame
else
move_ending_now = cmd if cmd[0] + cmd[1] == frame
move_overlapping_now = cmd if cmd[0] < frame && cmd[0] + cmd[1] > frame
end
end
new_command_needed = true
# Replace existing command at frame if it has a duration of 0
if set_now
set_now[2] = value
new_command_needed = false
end
# If a command has a duration >0 and ends at frame, replace its value
if move_ending_now
move_ending_now[2] = value
new_command_needed = false
end
return particle[property] if !new_command_needed
# Add a new command
new_cmd = [frame, 0, value]
particle[property].push(new_cmd)
# If the new command interrupts an interpolation, split that interpolation
if move_overlapping_now
end_frame = move_overlapping_now[0] + move_overlapping_now[1]
new_cmd[1] = end_frame - frame # Duration
new_cmd[2] = move_overlapping_now[2] # Value
new_cmd[3] = move_overlapping_now[3] # Interpolation type
move_overlapping_now[1] = frame - move_overlapping_now[0] # Duration
move_overlapping_now[2] = value # Value
end
# Sort and return the commands
particle[property].sort! { |a, b| a[0] == b[0] ? a[1] == b[1] ? 0 : a[1] <=> b[1] : a[0] <=> b[0] }
return particle[property]
end
# Cases:
# * SetXYZ - delete it
# * MoveXYZ start - turn into a SetXYZ at the end point
# * MoveXYZ end - delete it (this may happen to remove the start diamond too)
# * MoveXYZ end and start - merge both together (use first's type)
# * SetXYZ and MoveXYZ start - delete SetXYZ (leave MoveXYZ alone)
# * SetXYZ and MoveXYZ end - (unlikely) delete both
# * SetXYZ and MoveXYZ start and end - (unlikely) delete SetXYZ, merge Moves together
def delete_command(particle, property, frame)
# Find all relevant commands
set_now = nil
move_ending_now = nil
move_starting_now = nil
particle[property].each do |cmd|
if cmd[1] == 0
set_now = cmd if cmd[0] == frame
else
move_starting_now = cmd if cmd[0] == frame
move_ending_now = cmd if cmd[0] + cmd[1] == frame
end
end
# Delete SetXYZ if it is at frame
particle[property].delete(set_now) if set_now
# Edit/delete MoveXYZ commands starting/ending at frame
if move_ending_now && move_starting_now # Merge both MoveXYZ commands
move_ending_now[1] += move_starting_now[1] # Duration
move_ending_now[2] = move_starting_now[2] # Value
particle[property].delete(move_starting_now)
elsif move_ending_now # Delete MoveXYZ ending now
particle[property].delete(move_ending_now)
elsif move_starting_now && !set_now # Turn into SetXYZ at its end point
move_starting_now[0] += move_starting_now[1]
move_starting_now[1] = 0
move_starting_now[3] = nil
move_starting_now.compact!
end
return (particle[property].empty?) ? nil : particle[property]
end
# Removes commands for the particle's given property if they don't make a
# difference. Returns the resulting set of commands.
def optimize_commands(particle, property)
# Split particle[property] into values and interpolation arrays
set_points = [] # All SetXYZ commands (the values thereof)
end_points = [] # End points of MoveXYZ commands (the values thereof)
@@ -202,14 +289,6 @@ module AnimationEditor::ParticleDataHelper
end
end
end
# Add new command to points (may replace an existing command)
interp = :none
(frame + 1).times do |i|
interp = :none if set_points[i] || end_points[i]
interp = interps[i] if interps[i]
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
@@ -264,42 +343,60 @@ module AnimationEditor::ParticleDataHelper
return (ret.empty?) ? nil : ret
end
# Cases:
# * SetXYZ - delete it
# * MoveXYZ start - turn into a SetXYZ at the end point
# * MoveXYZ end - delete it (this may happen to remove the start diamond too)
# * MoveXYZ end and start - merge both together (use first's type)
# * SetXYZ and MoveXYZ start - delete SetXYZ (leave MoveXYZ alone)
# * SetXYZ and MoveXYZ end - (unlikely) delete both
# * SetXYZ and MoveXYZ start and end - (unlikely) delete SetXYZ, merge Moves together
def delete_command(particle, property, frame)
# Find all relevant commands
# SetXYZ at frame
# - none: Do nothing.
# - interp: Add MoveXYZ (calc duration/value at end).
# MoveXYZ at frame
# - none: Turn into two SetXYZ (MoveXYZ's value for end point, calc value
# for start point).
# - interp: Change type.
# SetXYZ and MoveXYZ at frame
# - none: Turn MoveXYZ into SetXYZ at the end point.
# - interp: Change MoveXYZ's type.
# End of earlier MoveXYZ (or nothing) at frame
# - none: Do nothing.
# - interp: Add MoveXYZ (calc duration/value at end).
def set_interpolation(particle, property, frame, type)
# Find relevant command
set_now = nil
move_ending_now = nil
move_starting_now = nil
particle[property].each do |cmd|
if cmd[1] == 0
set_now = cmd if cmd[0] == frame
next if cmd[0] != frame
set_now = cmd if cmd[1] == 0
move_starting_now = cmd if cmd[1] != 0
end
if move_starting_now
# If a MoveXYZ command exists at frame, amend it
if type == :none
old_end_point = move_starting_now[0] + move_starting_now[1]
old_value = move_starting_now[2]
# Turn the MoveXYZ command into a SetXYZ (or just delete it if a SetXYZ
# already exists at frame)
if set_now
particle[property].delete(move_starting_now)
else
move_starting_now[1] = 0
move_starting_now[2] = get_keyframe_particle_value(particle, property, frame)[0]
move_starting_now[3] = nil
move_starting_now.compact!
end
# Add a new SetXYZ at the end of the (former) interpolation
add_command(particle, property, old_end_point, old_value)
else
move_starting_now = cmd if cmd[0] == frame
move_ending_now = cmd if cmd[0] + cmd[1] == frame
# Simply change the type
move_starting_now[3] = type
end
elsif type != :none
# If no MoveXYZ command exists at frame, make one (if type isn't :none)
particle[property].each do |cmd| # Assumes commands are sorted by keyframe
next if cmd[0] <= frame
val_at_end = get_keyframe_particle_value(particle, property, cmd[0])[0]
particle[property].push([frame, cmd[0] - frame, val_at_end, type])
particle[property].sort! { |a, b| a[0] == b[0] ? a[1] == b[1] ? 0 : a[1] <=> b[1] : a[0] <=> b[0] }
break
end
end
# Delete SetXYZ if it is at frame
particle[property].delete(set_now) if set_now
# Edit/delete MoveXYZ commands starting/ending at frame
if move_ending_now && move_starting_now # Merge both MoveXYZ commands
move_ending_now[1] += move_starting_now[1]
particle[property].delete(move_starting_now)
elsif move_ending_now # Delete MoveXYZ ending now
particle[property].delete(move_ending_now)
elsif move_starting_now && !set_now # Turn into SetXYZ at its end point
move_starting_now[0] += move_starting_now[1]
move_starting_now[1] = 0
move_starting_now[3] = nil
move_starting_now.compact!
end
return (particle[property].empty?) ? nil : particle[property]
return particle[property]
end
#-----------------------------------------------------------------------------

View File

@@ -8,10 +8,15 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
LIST_X = 0
LIST_Y = TIMELINE_HEIGHT + VIEWPORT_SPACING
LIST_WIDTH = 180 - VIEWPORT_SPACING
EXPAND_BUTTON_X = VIEWPORT_SPACING
EXPAND_BUTTON_WIDTH = 19
LIST_BOX_X = EXPAND_BUTTON_X + EXPAND_BUTTON_WIDTH + VIEWPORT_SPACING
LIST_INDENT = 8
COMMANDS_X = LIST_WIDTH + VIEWPORT_SPACING
COMMANDS_Y = LIST_Y
ROW_HEIGHT = 24
ROW_SPACING = 1 # Gap at top of each row
DIAMOND_SIZE = 3
TIMELINE_LEFT_BUFFER = DIAMOND_SIZE + 1 # Allows diamonds at keyframe 0 to be drawn fully
TIMELINE_TEXT_SIZE = 16
@@ -19,6 +24,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
INTERP_LINE_HEIGHT = KEYFRAME_SPACING - ((DIAMOND_SIZE * 2) + 3)
INTERP_LINE_Y = (ROW_HEIGHT / 2) - (INTERP_LINE_HEIGHT / 2)
DURATION_BUFFER = 20 # Extra keyframes shown after the animation's end
PROPERTY_BG_COLOR = Color.new(224, 224, 224)
CONTROL_BG_COLORS = {
:foreground => Color.new(128, 160, 248), # Blue
:midground => Color.new(128, 160, 248), # Blue
@@ -32,7 +38,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
:target_side_background => Color.new(128, 248, 248) # Cyan
}
SE_CONTROL_BG_COLOR = Color.gray
TIME_AFTER_ANIMATION_COLOR = Color.new(224, 224, 224)
TIME_AFTER_ANIMATION_COLOR = Color.new(160, 160, 160)
attr_reader :keyframe # The selected keyframe
attr_reader :values
@@ -312,10 +318,14 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
end
def busy?
return true if @controls.any? { |c| c[1].busy? }
return true if controls_busy?
return super
end
def controls_busy?
return @controls.any? { |c| c[1].busy? }
end
def changed?
return @changed
end
@@ -366,7 +376,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
overall_commands = []
@particles[index].each_pair do |property, value|
next if !value.is_a?(Array)
cmds = AnimationEditor::ParticleDataHelper.get_particle_property_commands_timeline(@particles[index], value, property)
cmds = AnimationEditor::ParticleDataHelper.get_particle_property_commands_timeline(@particles[index], property, value)
@commands[[index, property]] = cmds
cmds.each_with_index do |cmd, i|
next if !cmd
@@ -468,22 +478,6 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
#-----------------------------------------------------------------------------
def property_display_name(property)
return {
:frame => _INTL("Frame"),
:blending => _INTL("Blending"),
: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"),
:visible => _INTL("Visible"),
:opacity => _INTL("Opacity")
}[property] || property.capitalize
end
def repaint
@list_scrollbar.repaint if @list_scrollbar.invalid?
@time_scrollbar.repaint if @time_scrollbar.invalid?
@@ -503,21 +497,23 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
@time_bg_sprite.bitmap.fill_rect(draw_x, TIMELINE_HEIGHT, greyed_width, VIEWPORT_SPACING, Color.black)
end
# Draw hover highlight
hover_color = nil
if @captured_keyframe && !@captured_row
if @hover_keyframe && @hover_keyframe == @captured_keyframe && !@hover_row
if !controls_busy?
hover_color = nil
if @captured_keyframe && !@captured_row
if @hover_keyframe && @hover_keyframe == @captured_keyframe && !@hover_row
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
end
draw_x = TIMELINE_LEFT_BUFFER + (@captured_keyframe * KEYFRAME_SPACING) - @left_pos
@timeline_sprite.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 0,
KEYFRAME_SPACING, TIMELINE_HEIGHT - 1, hover_color)
elsif !@captured_keyframe && !@captured_row && @hover_keyframe && !@hover_row
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
draw_x = TIMELINE_LEFT_BUFFER + (@hover_keyframe * KEYFRAME_SPACING) - @left_pos
@timeline_sprite.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 0,
KEYFRAME_SPACING, TIMELINE_HEIGHT - 1, hover_color)
end
draw_x = TIMELINE_LEFT_BUFFER + (@captured_keyframe * KEYFRAME_SPACING) - @left_pos
@timeline_sprite.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 0,
KEYFRAME_SPACING, TIMELINE_HEIGHT - 1, hover_color)
elsif !@captured_keyframe && !@captured_row && @hover_keyframe && !@hover_row
hover_color = HOVER_COLOR
draw_x = TIMELINE_LEFT_BUFFER + (@hover_keyframe * KEYFRAME_SPACING) - @left_pos
@timeline_sprite.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 0,
KEYFRAME_SPACING, TIMELINE_HEIGHT - 1, hover_color)
end
# Draw timeline markings
each_visible_keyframe(true) do |i|
@@ -551,37 +547,81 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
spr = @list_sprites[index]
return if !spr
spr.bitmap.clear
box_x = (@particle_list[index].is_a?(Array)) ? 16 : 0
# Get the background color
p_index = (@particle_list[index].is_a?(Array)) ? @particle_list[index][0] : @particle_list[index]
# Get useful information
is_property = @particle_list[index].is_a?(Array)
p_index = (is_property) ? @particle_list[index][0] : @particle_list[index]
particle_data = @particles[p_index]
box_x = LIST_BOX_X
box_x += LIST_INDENT if is_property
# Get the background color
if particle_data[:name] == "SE"
bg_color = SE_CONTROL_BG_COLOR
elsif is_property
bg_color = PROPERTY_BG_COLOR
else
bg_color = CONTROL_BG_COLORS[@particles[p_index][:focus]] || Color.magenta
end
# Draw hover highlight
hover_color = nil
if @captured_row && !@captured_keyframe
if @captured_row == index
if @hover_row && @hover_row == index && !@hover_keyframe
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
if !controls_busy? && !@captured_keyframe
hover_color = nil
if @captured_row
if @captured_row == index
if !@hover_keyframe && @hover_row && @hover_row == index &&
@captured_row_button && @hover_row_button == @captured_row_button
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
end
end
elsif @hover_row && @hover_row == index && !@hover_keyframe
hover_color = HOVER_COLOR
end
if hover_color
case @captured_row_button || @hover_row_button
when :expand
spr.bitmap.fill_rect(EXPAND_BUTTON_X, (ROW_HEIGHT - EXPAND_BUTTON_WIDTH + 1) / 2,
EXPAND_BUTTON_WIDTH, EXPAND_BUTTON_WIDTH, hover_color)
when :row
spr.bitmap.fill_rect(box_x, ROW_SPACING, spr.width - box_x, spr.height - ROW_SPACING, hover_color)
end
end
elsif !@captured_row && !@captured_keyframe && @hover_row && @hover_row == index && !@hover_keyframe
hover_color = HOVER_COLOR
end
spr.bitmap.fill_rect(box_x, 1, spr.width - box_x, spr.height - 1, hover_color) if hover_color
# Draw outline
spr.bitmap.outline_rect(box_x, 1, spr.width - box_x, spr.height - 1, bg_color, 2)
spr.bitmap.outline_rect(box_x, ROW_SPACING, spr.width - box_x, spr.height - ROW_SPACING, bg_color, 2)
# Draw text
if @particle_list[index].is_a?(Array)
draw_text(spr.bitmap, box_x + 4, 0, "") # ►
draw_text(spr.bitmap, box_x + 4 + 17, 3, property_display_name(@particle_list[index][1]))
if is_property
draw_text(spr.bitmap, box_x + 4, 3, GameData::Animation.property_display_name(@particle_list[index][1]) + ":")
else
draw_text(spr.bitmap, 4, 3, @particles[p_index][:name] || "Unnamed")
draw_text(spr.bitmap, box_x + 4, 3, @particles[p_index][:name] || "Unnamed")
end
# Draw expand/collapse arrow or dotted lines
if is_property
6.times do |j|
spr.bitmap.fill_rect(10, j * 2, 1, 1, Color.black)
end
9.times do |i|
spr.bitmap.fill_rect(10 + (i * 2), 12, 1, 1, Color.black)
end
elsif @expanded_particles.include?(p_index)
11.times do |i|
j = (i == 0 || i == 10) ? 1 : 0
h = [2, 4, 5, 6, 7, 8, 7, 6, 5, 4, 2][i]
h = ((i > 5) ? 10 - i : i) + 3 - j
spr.bitmap.fill_rect(5 + i, 9 + j, 1, h, Color.black)
end
elsif particle_data[:name] != "SE"
11.times do |j|
i = (j == 0 || j == 10) ? 1 : 0
w = [2, 4, 5, 6, 7, 8, 7, 6, 5, 4, 2][j]
w = ((j > 5) ? 10 - j : j) + 3 - i
spr.bitmap.fill_rect(7 + i, 7 + j, w, 1, Color.black)
end
end
# Draw dotted line leading to the next property line
if @particle_list[index + 1]&.is_a?(Array)
5.times do |j|
spr.bitmap.fill_rect(10, 14 + (j * 2), 1, 1, Color.black)
end
end
end
@@ -589,11 +629,14 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
bg_spr = @commands_bg_sprites[index]
return if !bg_spr
bg_spr.bitmap.clear
p_index = (@particle_list[index].is_a?(Array)) ? @particle_list[index][0] : @particle_list[index]
is_property = @particle_list[index].is_a?(Array)
p_index = (is_property) ? @particle_list[index][0] : @particle_list[index]
particle_data = @particles[p_index]
# Get the background color
if particle_data[:name] == "SE"
bg_color = SE_CONTROL_BG_COLOR
elsif is_property
bg_color = PROPERTY_BG_COLOR
else
bg_color = CONTROL_BG_COLORS[@particles[p_index][:focus]] || Color.magenta
end
@@ -604,33 +647,53 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
draw_x = TIMELINE_LEFT_BUFFER + (i * KEYFRAME_SPACING) - @left_pos
# Draw bg
if i < @duration - DURATION_BUFFER && visible_cmds[i]
bg_spr.bitmap.fill_rect(draw_x, 1, KEYFRAME_SPACING, ROW_HEIGHT - 2, bg_color)
bg_spr.bitmap.fill_rect(draw_x, ROW_SPACING, KEYFRAME_SPACING, ROW_HEIGHT - ROW_SPACING, bg_color)
end
# Draw hover highlight
hover_color = nil
if @captured_row && @captured_keyframe
if @captured_row == index && @captured_keyframe == i
if @hover_row && @hover_row == index && @hover_keyframe && @hover_keyframe == i
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
end
if !controls_busy?
earlier_captured_keyframe = @captured_keyframe
later_captured_keyframe = (earlier_captured_keyframe || -1) + 1
earlier_hovered_keyframe = @hover_keyframe
later_hovered_keyframe = (earlier_hovered_keyframe || -1) + 1
if is_property
later_captured_keyframe = @captured_row_button || 0
later_hovered_keyframe = @hover_row_button || 0
end
if @captured_row && @captured_keyframe
if @captured_row == index && i >= earlier_captured_keyframe && i < later_captured_keyframe
if @hover_row && @hover_row == index && @hover_keyframe && i >= earlier_hovered_keyframe && i < later_hovered_keyframe
hover_color = HOVER_COLOR
else
hover_color = CAPTURE_COLOR
end
end
elsif !@captured_row && !@captured_keyframe && @hover_row && @hover_keyframe &&
@hover_row == index && i >= earlier_hovered_keyframe && i < later_hovered_keyframe
hover_color = HOVER_COLOR
end
end
if hover_color
if is_property
bg_spr.bitmap.fill_rect(draw_x, 2, KEYFRAME_SPACING, ROW_HEIGHT - 3, hover_color)
else
bg_spr.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 2, KEYFRAME_SPACING, ROW_HEIGHT - 3, hover_color)
end
elsif !@captured_row && !@captured_keyframe &&
@hover_row && @hover_row == index && @hover_keyframe && @hover_keyframe == i
hover_color = HOVER_COLOR
end
bg_spr.bitmap.fill_rect(draw_x - (KEYFRAME_SPACING / 2), 2, KEYFRAME_SPACING, ROW_HEIGHT - 3, hover_color) if hover_color
next if i >= @duration - DURATION_BUFFER
next if !visible_cmds[i]
# Draw outline
bg_spr.bitmap.fill_rect(draw_x, 1, KEYFRAME_SPACING, 1, Color.black) # Top
bg_spr.bitmap.fill_rect(draw_x, ROW_HEIGHT - 1, KEYFRAME_SPACING, 1, Color.black) # Bottom
outline_color = Color.black
if is_property
outline_color = CONTROL_BG_COLORS[@particles[p_index][:focus]] || Color.magenta
end
bg_spr.bitmap.fill_rect(draw_x, ROW_SPACING, KEYFRAME_SPACING, 1, outline_color) # Top
bg_spr.bitmap.fill_rect(draw_x, ROW_HEIGHT - 1, KEYFRAME_SPACING, 1, outline_color) # Bottom
if i <= 0 || !visible_cmds[i - 1]
bg_spr.bitmap.fill_rect(draw_x, 1, 1, ROW_HEIGHT - 1, Color.black) # Left
bg_spr.bitmap.fill_rect(draw_x, ROW_SPACING, 1, ROW_HEIGHT - ROW_SPACING, outline_color) # Left
end
if i == @duration - DURATION_BUFFER - 1 || (i < @duration - 1 && !visible_cmds[i + 1])
bg_spr.bitmap.fill_rect(draw_x + KEYFRAME_SPACING, 1, 1, ROW_HEIGHT - 1, Color.black) # Right
bg_spr.bitmap.fill_rect(draw_x + KEYFRAME_SPACING, ROW_SPACING, 1, ROW_HEIGHT - ROW_SPACING, outline_color) # Right
end
end
end
@@ -714,17 +777,47 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
listed_element = @particle_list[new_hover_row]
p_index = listed_element.is_a?(Array) ? listed_element[0] : listed_element
break if @particles[p_index][:name] == "SE"
ret = [area, nil, new_hover_row]
mouse_y_in_row = mouse_y + @top_pos - (new_hover_row * ROW_HEIGHT) - rect.y
case mouse_x
when EXPAND_BUTTON_X...(EXPAND_BUTTON_X + EXPAND_BUTTON_WIDTH)
next if listed_element.is_a?(Array)
next if mouse_y_in_row < (ROW_HEIGHT - EXPAND_BUTTON_WIDTH + 1) / 2
next if mouse_y_in_row >= ((ROW_HEIGHT - EXPAND_BUTTON_WIDTH + 1) / 2) + EXPAND_BUTTON_WIDTH
ret = [area, nil, new_hover_row, :expand]
when LIST_BOX_X...(@list_viewport.rect.width)
next if listed_element.is_a?(Array)
next if mouse_y_in_row < ROW_SPACING
ret = [area, nil, new_hover_row, :row]
end
when :timeline
new_hover_keyframe = (mouse_x + @left_pos - rect.x - TIMELINE_LEFT_BUFFER + (KEYFRAME_SPACING / 2) - 1) / KEYFRAME_SPACING
break if new_hover_keyframe < 0 || new_hover_keyframe >= @duration
ret = [area, new_hover_keyframe, nil]
when :commands
new_hover_row = (mouse_y + @top_pos - rect.y) / ROW_HEIGHT
new_hover_keyframe = (mouse_x + @left_pos - rect.x - TIMELINE_LEFT_BUFFER + (KEYFRAME_SPACING / 2) - 1) / KEYFRAME_SPACING
break if new_hover_row >= @particle_list.length
listed_element = @particle_list[new_hover_row]
if listed_element.is_a?(Array)
new_hover_keyframe = (mouse_x + @left_pos - rect.x - TIMELINE_LEFT_BUFFER - 1) / KEYFRAME_SPACING
else
new_hover_keyframe = (mouse_x + @left_pos - rect.x - TIMELINE_LEFT_BUFFER + (KEYFRAME_SPACING / 2) - 1) / KEYFRAME_SPACING
end
break if new_hover_keyframe < 0 || new_hover_keyframe >= @duration
ret = [area, new_hover_keyframe, new_hover_row]
if listed_element.is_a?(Array)
break if !GameData::Animation.property_can_interpolate?(listed_element[1])
cmds = @commands[listed_element]
break if !cmds
earlier_keyframe = nil
later_keyframe = nil
cmds.each_with_index do |cmd, i|
earlier_keyframe = i if cmd && i <= new_hover_keyframe
later_keyframe = i if cmd && !later_keyframe && i > new_hover_keyframe
end
break if !earlier_keyframe || !later_keyframe
ret = [area, earlier_keyframe, new_hover_row, later_keyframe]
else
ret = [area, new_hover_keyframe, new_hover_row]
end
end
break
end
@@ -738,6 +831,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
@captured_area = hover_element[0]
@captured_keyframe = hover_element[1]
@captured_row = hover_element[2]
@captured_row_button = hover_element[3]
end
end
@@ -748,19 +842,38 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
if hover_element.is_a?(Array)
if @captured_area == hover_element[0] &&
@captured_keyframe == hover_element[1] &&
@captured_row == hover_element[2]
if @captured_row && @particle_list[@captured_row].is_a?(Array)
# TODO: If I want to be able to select individual property rows and/or
# diamonds, I shouldn't have this line.
@captured_row = @particle_list.index(@particle_list[@captured_row][0])
@captured_row == hover_element[2] &&
@captured_row_button == hover_element[3]
if @captured_area == :commands && @captured_row && @particle_list[@captured_row].is_a?(Array)
set_changed
@values[:cycle_interpolation] = [*@particle_list[@captured_row], @captured_keyframe]
else
case @captured_row_button
when :expand
particle_index = @particle_list[@captured_row]
particle_index = particle_index[0] if particle_index.is_a?(Array)
if @expanded_particles.include?(particle_index) # Contract
@expanded_particles.delete(particle_index)
else # Expand
@expanded_particles.push(particle_index)
end
set_particles(@particles)
else # :row button or somewhere in the commands area or timeline, just change selection
if @captured_row && @particle_list[@captured_row].is_a?(Array)
@captured_row = @particle_list.index(@particle_list[@captured_row][0])
end
set_changed if @keyframe != @captured_keyframe || @row_index != @captured_row
@keyframe = @captured_keyframe || -1
# TODO: If :keyframe_pane should be accessible by clicking on the
# timeline, change the below line to = @captured_row || -1.
@row_index = @captured_row if @captured_row
end
end
set_changed if @keyframe != @captured_keyframe || @row_index != @captured_row
@keyframe = @captured_keyframe || -1
@row_index = @captured_row || -1
end
end
@captured_keyframe = nil
@captured_row = nil
@captured_row_button = nil
super # Make this control not busy again
end
@@ -778,6 +891,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
@hover_area = nil
@hover_keyframe = nil
@hover_row = nil
@hover_row_button = nil
return
end
# Check each interactive area for whether the mouse is hovering over it, and
@@ -787,7 +901,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
invalidate if @hover_area != hover_element[0] # Moved to a different region
case hover_element[0]
when :list
invalidate_rows if @hover_row != hover_element[2]
invalidate_rows if @hover_row != hover_element[2] || @hover_row_button != hover_element[3]
when :timeline
invalidate_time if @hover_keyframe != hover_element[1]
when :commands
@@ -797,6 +911,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
@hover_area = hover_element[0]
@hover_keyframe = hover_element[1]
@hover_row = hover_element[2]
@hover_row_button = hover_element[3]
elsif hover_element
if @hover_area == hover_element
case @hover_area
@@ -813,11 +928,13 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
@hover_area = hover_element
@hover_keyframe = nil
@hover_row = nil
@hover_row_button = nil
else
invalidate if @hover_area
@hover_area = nil
@hover_keyframe = nil
@hover_row = nil
@hover_row_button = nil
end
end
@@ -825,7 +942,9 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
return if !self.visible
@list_scrollbar.update
@time_scrollbar.update
@controls.each { |c| c[1].update }
if !@captured_area
@controls.each { |c| c[1].update }
end
super
# Refresh sprites if a scrollbar has been moved
self.top_pos = @list_scrollbar.position