mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-07 13:15:01 +00:00
Anim Editor: added interpolation editing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user