Tidied up TODO comments, misc tweaks to Anim Editor

This commit is contained in:
Maruno17
2023-12-03 23:47:38 +00:00
parent b4e7b765d1
commit 2f231a25bb
203 changed files with 813 additions and 852 deletions

View File

@@ -1,16 +1,6 @@
#===============================================================================
# Controls are arranged in a list in self's bitmap. Each control is given an
# area of size "self's bitmap's width" x LINE_SPACING to draw itself in.
# TODO: The act of "capturing" a control makes other controls in this container
# not update themselves, i.e. they won't colour themselves with a hover
# highlight if the mouse happens to move over it while another control is
# captured. Is there a better way of dealing with this? I'm leaning
# towards the control itself deciding if it's captured, and it being
# treated as uncaptured once it says its value has changed, but I think
# this would require manually telling all other controls in this container
# that something else is captured and they shouldn't show a hover
# highlight when updated (perhaps as a parameter in def update), which I
# don't think is ideal.
#===============================================================================
class UIControls::ControlsContainer
attr_reader :x, :y
@@ -171,11 +161,6 @@ class UIControls::ControlsContainer
return if !@visible
# Update controls
if @captured
# TODO: Ideally all controls will be updated here, if only to redraw
# themselves if they happen to be invalidated somehow. But that
# involves telling each control whether any other control is busy,
# to ensure that they don't show their hover colours or anything,
# which is fiddly and I'm not sure if it's the best approach.
@captured.update
@captured = nil if !@captured.busy?
else

View File

@@ -10,6 +10,7 @@ class UIControls::BaseControl < BitmapSprite
TEXT_COLOR = Color.black
TEXT_SIZE = 18 # Default is 22 if size isn't explicitly set
TEXT_OFFSET_Y = 5
HOVER_COLOR = Color.cyan # For clickable area when hovering over it
CAPTURE_COLOR = Color.pink # For area you clicked in but aren't hovering over
DISABLED_COLOR = Color.gray
@@ -19,7 +20,6 @@ class UIControls::BaseControl < BitmapSprite
super(width, height, viewport)
self.bitmap.font.color = TEXT_COLOR
self.bitmap.font.size = TEXT_SIZE
@disabled = false
@hover_area = nil # Is a symbol from the keys for @interactions if the mouse is hovering over that interaction
@captured_area = nil # Is a symbol from the keys for @interactions (or :none) if this control is clicked in
@@ -51,10 +51,7 @@ class UIControls::BaseControl < BitmapSprite
return false if !@interactions || @interactions.empty?
mouse_x, mouse_y = mouse_pos
return false if !mouse_x || !mouse_y
@interactions.each_pair do |area, rect|
return true if rect.contains?(mouse_x, mouse_y)
end
return false
return @interactions.any? { |area, rect| rect.contains?(mouse_x, mouse_y) }
end
#-----------------------------------------------------------------------------
@@ -84,7 +81,7 @@ class UIControls::BaseControl < BitmapSprite
@invalid = true
end
# Makes the control no longer invalid.
# Makes the control no longer invalid. Called after repainting.
def validate
@invalid = false
end
@@ -125,7 +122,6 @@ class UIControls::BaseControl < BitmapSprite
end
def refresh
# Paint over control to erase contents (intentionally not using self.bitmap.clear)
self.bitmap.clear
draw_area_highlight
end
@@ -194,10 +190,8 @@ class UIControls::BaseControl < BitmapSprite
# Updates the logic on the control, invalidating it if necessary.
def update
return if !self.visible
return if disabled? && !busy?
return if disabled? && !busy? # This control still works if it becomes disabled while using it
update_hover_highlight
# Detect a mouse press/release
if @interactions && !@interactions.empty?
if Input.trigger?(Input::MOUSELEFT)
@@ -206,6 +200,5 @@ class UIControls::BaseControl < BitmapSprite
on_mouse_release
end
end
end
end

View File

@@ -4,9 +4,6 @@
class UIControls::Label < UIControls::BaseControl
attr_reader :text
LABEL_END_X = 80
TEXT_OFFSET_Y = 5
def initialize(width, height, viewport, text)
super(width, height, viewport)
@text = text
@@ -27,8 +24,10 @@ class UIControls::Label < UIControls::BaseControl
super
if @header
draw_text_centered(self.bitmap, 0, TEXT_OFFSET_Y, width, @text)
# Draw underline
text_size = self.bitmap.text_size(@text)
self.bitmap.fill_rect((width - text_size.width) / 2, TEXT_OFFSET_Y + text_size.height, text_size.width, 1, TEXT_COLOR)
self.bitmap.fill_rect((width - text_size.width) / 2, TEXT_OFFSET_Y + text_size.height,
text_size.width, 1, TEXT_COLOR)
else
draw_text(self.bitmap, 4, TEXT_OFFSET_Y, @text)
end

View File

@@ -1,20 +1,18 @@
#===============================================================================
# TODO: Support selecting part of the text by remembering the initial
# cursor position and using it and the current cursor position to
# decide which characters are selected. Maybe? Note that this method
# is only triggered upon the initial mouse press, and isn't repeated
# while it's still held down.
#
#===============================================================================
class UIControls::TextBox < UIControls::BaseControl
attr_accessor :box_width
TEXT_BOX_X = 2
TEXT_BOX_WIDTH = 200
TEXT_BOX_HEIGHT = 24
TEXT_BOX_PADDING = 4 # Gap between sides of text box and text
TEXT_OFFSET_Y = 5
def initialize(width, height, viewport, value = "")
super(width, height, viewport)
@value = value
@box_width = TEXT_BOX_WIDTH
@cursor_pos = -1
@display_pos = 0
@cursor_timer = nil
@@ -22,9 +20,13 @@ class UIControls::TextBox < UIControls::BaseControl
@blacklist = []
end
def value
return @value.dup
end
def value=(new_value)
return if @value == new_value
@value = new_value
@value = new_value.dup
invalidate
end
@@ -59,7 +61,7 @@ class UIControls::TextBox < UIControls::BaseControl
def set_interactive_rects
@text_box_rect = Rect.new(TEXT_BOX_X, (height - TEXT_BOX_HEIGHT) / 2,
[TEXT_BOX_WIDTH, width].min, TEXT_BOX_HEIGHT)
[@box_width, width - (TEXT_BOX_X * 2)].min, TEXT_BOX_HEIGHT)
@interactions = {
:text_box => @text_box_rect
}

View File

@@ -1,6 +1,5 @@
#===============================================================================
# TODO: Is there a better knob design than a big black rectangle? I'd rather
# it not be a different colour.
#
#===============================================================================
class UIControls::NumberSlider < UIControls::BaseControl
attr_reader :min_value
@@ -14,7 +13,6 @@ class UIControls::NumberSlider < UIControls::BaseControl
SLIDER_LENGTH = 128
PLUS_X = SLIDER_X + SLIDER_LENGTH + SLIDER_PADDING
VALUE_X = PLUS_X + PLUS_MINUS_SIZE + 5
TEXT_OFFSET_Y = 5
def initialize(width, height, viewport, min_value, max_value, value)
super(width, height, viewport)

View File

@@ -4,8 +4,8 @@
class UIControls::Button < UIControls::BaseControl
BUTTON_X = 2
BUTTON_Y = 2
BUTTON_PADDING = 10
BUTTON_HEIGHT = 28
BUTTON_PADDING = 10 # Used when @fixed_size is false
BUTTON_HEIGHT = 28 # Used when @fixed_size is false
# TODO: This will also depend on the font size.
TEXT_BASE_OFFSET_Y = 18 # Text is centred vertically in the button
@@ -20,6 +20,7 @@ class UIControls::Button < UIControls::BaseControl
end
def set_interactive_rects
@interactions&.clear
button_width = (@fixed_size) ? width - (BUTTON_X * 2) : self.bitmap.text_size(@text).width + (BUTTON_PADDING * 2)
button_height = (@fixed_size) ? height - (2 * BUTTON_Y) : BUTTON_HEIGHT
button_height = [button_height, height - (2 * BUTTON_Y)].min
@@ -29,10 +30,10 @@ class UIControls::Button < UIControls::BaseControl
}
end
# TODO: This won't change the button's size. This is probably okay.
def set_text(val)
return if @text == val
@text = val
set_interactive_rects if !@fixed_size
invalidate
end
@@ -60,12 +61,14 @@ class UIControls::Button < UIControls::BaseControl
self.bitmap.outline_rect(@button_rect.x, @button_rect.y,
@button_rect.width, @button_rect.height,
self.bitmap.font.color)
# TODO: Make buttons look more different to text boxes?
# shade = self.bitmap.font.color.clone
# shade.alpha = 96
# self.bitmap.outline_rect(@button_rect.x + 1, @button_rect.y + 1,
# @button_rect.width - 2, @button_rect.height - 2,
# shade, 3)
# Draw inner grey ring that shows this is a button rather than a text box
if !disabled?
shade = self.bitmap.font.color.clone
shade.alpha = 64
self.bitmap.outline_rect(@button_rect.x + 2, @button_rect.y + 2,
@button_rect.width - 4, @button_rect.height - 4,
shade, 1)
end
# Draw button text
draw_text_centered(self.bitmap, @button_rect.x,
@button_rect.y + (@button_rect.height - TEXT_BASE_OFFSET_Y) / 2,

View File

@@ -1,11 +1,5 @@
#===============================================================================
# TODO: Do I need to split self's bitmap into two (one for highlights and one
# for text)? This would be to reduce lag caused by redrawing text even if
# you're just waving the mouse over the control. There doesn't seem to be
# any lag at the moment with a tall list.
# TODO: Make a viewport for the list, and allow scrolling positions halfway
# through a line? Nah.
# TODO: This control cannot be disabled.
#
#===============================================================================
class UIControls::List < UIControls::BaseControl
LIST_X = 0

View File

@@ -2,22 +2,23 @@
#
#===============================================================================
class UIControls::DropdownList < UIControls::BaseControl
attr_accessor :box_width
attr_accessor :max_rows
TEXT_BOX_X = 2
TEXT_BOX_WIDTH = 200
TEXT_BOX_HEIGHT = 24
TEXT_BOX_PADDING = 4 # Gap between sides of text box and text
TEXT_OFFSET_Y = 5
MAX_LIST_ROWS = 8
attr_accessor :max_rows
# NOTE: options is a hash: keys are symbols, values are display names.
def initialize(width, height, viewport, options, value)
# NOTE: options is a hash: keys are symbols, values are display names.
super(width, height, viewport)
@options = options
@value = value
@options = options
@value = value
@box_width = TEXT_BOX_WIDTH
@toggling_dropdown_list = false
@max_rows = MAX_LIST_ROWS
@max_rows = MAX_LIST_ROWS
end
def dispose
@@ -33,7 +34,7 @@ class UIControls::DropdownList < UIControls::BaseControl
def set_interactive_rects
@button_rect = Rect.new(TEXT_BOX_X, (height - TEXT_BOX_HEIGHT) / 2,
[TEXT_BOX_WIDTH, width].min, TEXT_BOX_HEIGHT)
[@box_width, width - (TEXT_BOX_X * 2)].min, TEXT_BOX_HEIGHT)
@interactions = {
:button => @button_rect
}

View File

@@ -1,7 +1,5 @@
#===============================================================================
# TODO: Make the slider a separate sprite that moves, instead of redrawing this
# sprite's bitmap whenever it moves? Intended to reduce lag. There doesn't
# seem to be any lag at the moment with a tall scrollbar.
#
#===============================================================================
class UIControls::Scrollbar < UIControls::BaseControl
SLIDER_WIDTH = 16
@@ -122,9 +120,6 @@ class UIControls::Scrollbar < UIControls::BaseControl
return if !self.visible
super
if @captured_area == :slider
# TODO: Have a display y position for the slider bar which is in pixels,
# and round it to the nearest row when setting @top_row? This is
# just to make the slider bar movement smoother.
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
long_coord = (@horizontal) ? mouse_x : mouse_y

View File

@@ -6,6 +6,15 @@ class Bitmap
fill_rect(x + width - thickness, y, thickness, height, color)
end
# Draws a series of concentric outline_rects around the defined area. From
# inside to outside, the color of each ring alternates.
def border_rect(x, y, width, height, thickness, color1, color2)
thickness.times do |i|
col = (i.even?) ? color1 : color2
outline_rect(x - i - 1, y - i - 1, width + (i * 2) + 2, height + (i * 2) + 2, col)
end
end
def fill_diamond(x, y, radius, color)
((radius * 2) + 1).times do |i|
height = (i <= radius) ? (i * 2) + 1 : (((radius * 2) - i) * 2) + 1

View File

@@ -25,7 +25,6 @@ module GameData
# Properties that apply to the animation in general, not to individual
# particles. They don't change during the animation.
SCHEMA = {
# TODO: Add support for overworld animations.
"SectionName" => [:id, "esU", {"Move" => :move, "OppMove" => :opp_move,
"Common" => :common, "OppCommon" => :opp_common}],
"Name" => [:name, "s"],
@@ -36,8 +35,6 @@ module GameData
# TODO: DamageFrame (keyframe at which the battle continues, i.e. damage
# animations start playing).
"Flags" => [:flags, "*s"],
# TODO: If this is changed to be more than just a string, edit the
# compiler's current_particle definition accordingly.
"Particle" => [:particles, "s"] # Is a subheader line like <text>
}
# For individual particles. Any property whose schema begins with "^" can
@@ -49,9 +46,9 @@ module GameData
# These properties cannot be changed partway through the animation.
# NOTE: "Name" isn't a property here, because the particle's name comes
# from the "Particle" property above.
"Graphic" => [:graphic, "s"],
# TODO: If more focus types are added, add ones that involve a target to
# the Compiler's check relating to "NoTarget".
"Graphic" => [:graphic, "s"],
"Focus" => [:focus, "e", {"User" => :user, "Target" => :target,
"UserAndTarget" => :user_and_target,
"Screen" => :screen}],
@@ -59,8 +56,8 @@ module GameData
# All properties below are "SetXYZ" or "MoveXYZ". "SetXYZ" has the
# keyframe and the value, and "MoveXYZ" has the keyframe, duration and the
# value. All are "^". "SetXYZ" is turned into "MoveXYZ" when compiling by
# inserting a duration (second value) of 0.
# value. All have "^" in their schema. "SetXYZ" is turned into "MoveXYZ"
# when compiling by inserting a duration (second value) of 0.
"SetFrame" => [:frame, "^uu"], # Frame within the graphic if it's a spritesheet
"MoveFrame" => [:frame, "^uuuE", nil, nil, nil, INTERPOLATION_TYPES],
"SetBlending" => [:blending, "^uu"], # 0, 1 or 2
@@ -144,7 +141,7 @@ module GameData
:target_cry => nil
}
@@cmd_to_pbs_name = nil # USed for writing animation PBS files
@@cmd_to_pbs_name = nil # Used for writing animation PBS files
extend ClassMethodsIDNumbers
include InstanceMethods
@@ -162,27 +159,6 @@ module GameData
DATA[(id_num >= 0) ? id_num : DATA.keys.length] = self.new(hash)
end
# TODO: Rewrite this to query animations from other criteria. Remember that
# multiple animations could have the same move/version. Odds are this
# method won't be used much at all.
# TODO: Separate exists? methods for move and common animations?
# def exists?(other)
# end
# TODO: Rewrite this to get animations from other criteria. Remember that
# multiple animations could have the same move/version. Odds are this
# method won't be used much at all.
# TODO: Separate get methods for move and common animations?
# def get(other)
# end
# TODO: Rewrite this to get animations from other criteria. Remember that
# multiple animations could have the same move/version. Odds are this
# method won't be used much at all.
# TODO: Separate try_get methods for move and common animations?
# def try_get(other)
# end
def initialize(hash)
# NOTE: hash has an :id entry, but it's unused here.
@type = hash[:type]
@@ -201,9 +177,9 @@ module GameData
def clone_as_hash
ret = {}
ret[:type] = @type
ret[:move] = @move.dup
ret[:version] = @version.dup
ret[:name] = @name.dup
ret[:move] = @move
ret[:version] = @version
ret[:name] = @name
ret[:particles] = [] # Clone the @particles array, which is nested hashes and arrays
@particles.each do |particle|
new_p = {}
@@ -254,10 +230,6 @@ module GameData
# The User and Target particles have hardcoded graphics/foci, so they
# don't need writing to PBS
ret = nil if ["User", "Target"].include?(@particles[index][:name])
when "Play"
# TODO: Turn volume/pitch of 100 into nil.
when "PlayUserCry", "PlayTargetCry"
# TODO: Turn volume/pitch of 100 into nil.
when "AllCommands"
# Get translations of all properties to their names as seen in PBS
# animation files
@@ -283,6 +255,14 @@ module GameData
new_cmd.pop if new_cmd.last == :linear # This is the default
ret.push([@@cmd_to_pbs_name[key][1]] + new_cmd) # ["MoveXYZ", keyframe, duration, value]
else
case key
when :se
new_cmd[4] = nil if new_cmd[4] == 100 # Pitch
new_cmd[3] = nil if new_cmd[4].nil? && new_cmd[3] == 100 # Volume
when :user_cry, :target_cry
new_cmd[3] = nil if new_cmd[3] == 100 # Pitch
new_cmd[2] = nil if new_cmd[3].nil? && new_cmd[2] == 100 # Volume
end
ret.push([@@cmd_to_pbs_name[key][0]] + new_cmd) # ["SetXYZ", keyframe, duration, value]
end
end

View File

@@ -1,20 +1,5 @@
# TODO: Should I split this code into visual and mechanical classes, a la the
# other UI screens?
#===============================================================================
# TODO: When creating a new particle, blacklist the names "User", "Target" and
# "SE". Make particles with those names undeletable.
# TODO: Remove the particle named "Target" if the animation's focus is changed
# to one that doesn't include a target, and vice versa. Do the same for
# "User"(?).
# TODO: Things that need pop-up windows (draws a semi-transparent grey over the
# whole screen behind the window):
# - animation properties (Move/OppMove/Common/OppCommon, move, version,
# extra name, target, filepath, flags, etc.)
# - editor settings (theme, canvas BG graphics, user/target graphics,
# display of canvas particle boxes, etc.)
# TODO: While playing the animation, draw a semi-transparent grey over the
# screen except for the canvas and playback controls. Can't edit anything
# while it's playing.
#
#===============================================================================
class AnimationEditor
WINDOW_WIDTH = Settings::SCREEN_WIDTH + (32 * 10)
@@ -22,6 +7,7 @@ class AnimationEditor
BORDER_THICKNESS = 4
# Components
MENU_BAR_WIDTH = WINDOW_WIDTH
MENU_BAR_HEIGHT = 30
@@ -45,15 +31,43 @@ class AnimationEditor
PARTICLE_LIST_WIDTH = WINDOW_WIDTH - (BORDER_THICKNESS * 2)
PARTICLE_LIST_HEIGHT = WINDOW_HEIGHT - PARTICLE_LIST_Y - BORDER_THICKNESS
ANIM_PROPERTIES_WIDTH = SIDE_PANE_WIDTH + 80
ANIM_PROPERTIES_HEIGHT = WINDOW_HEIGHT * 3 / 4
ANIM_PROPERTIES_X = (WINDOW_WIDTH - ANIM_PROPERTIES_WIDTH) / 2
ANIM_PROPERTIES_Y = (WINDOW_HEIGHT - ANIM_PROPERTIES_HEIGHT) / 2
# Pop-up windows
ANIM_PROPERTIES_LABEL_WIDTH = UIControls::ControlsContainer::OFFSET_FROM_LABEL_X + 80
ANIM_PROPERTIES_WIDTH = SIDE_PANE_WIDTH + 80 + 8
ANIM_PROPERTIES_HEIGHT = WINDOW_HEIGHT * 3 / 4
ANIM_PROPERTIES_X = (WINDOW_WIDTH - ANIM_PROPERTIES_WIDTH) / 2
ANIM_PROPERTIES_Y = (WINDOW_HEIGHT - ANIM_PROPERTIES_HEIGHT) / 2
MESSAGE_BOX_WIDTH = WINDOW_WIDTH * 3 / 4
MESSAGE_BOX_HEIGHT = 160
MESSAGE_BOX_BUTTON_WIDTH = 150
MESSAGE_BOX_BUTTON_HEIGHT = 32
MESSAGE_BOX_SPACING = 16
CHOOSER_BUTTON_WIDTH = 150
CHOOSER_BUTTON_HEIGHT = MESSAGE_BOX_BUTTON_HEIGHT
CHOOSER_FILE_LIST_X = 8
CHOOSER_FILE_LIST_Y = 32
CHOOSER_FILE_LIST_WIDTH = CHOOSER_BUTTON_WIDTH * 2
CHOOSER_FILE_LIST_HEIGHT = UIControls::List::ROW_HEIGHT * 15
GRAPHIC_CHOOSER_PREVIEW_SIZE = 320 # Square
GRAPHIC_CHOOSER_WINDOW_WIDTH = CHOOSER_FILE_LIST_X + CHOOSER_FILE_LIST_WIDTH + 10 + GRAPHIC_CHOOSER_PREVIEW_SIZE + 8 + (BORDER_THICKNESS * 2)
GRAPHIC_CHOOSER_WINDOW_HEIGHT = CHOOSER_FILE_LIST_Y + CHOOSER_FILE_LIST_HEIGHT + 10 + CHOOSER_BUTTON_HEIGHT + 8 + (BORDER_THICKNESS * 2)
GRAPHIC_CHOOSER_X = ((WINDOW_WIDTH - GRAPHIC_CHOOSER_WINDOW_WIDTH) / 2)
GRAPHIC_CHOOSER_Y = ((WINDOW_HEIGHT - GRAPHIC_CHOOSER_WINDOW_HEIGHT) / 2)
AUDIO_CHOOSER_LABEL_WIDTH = UIControls::ControlsContainer::OFFSET_FROM_LABEL_X
AUDIO_CHOOSER_SLIDER_WIDTH = (CHOOSER_BUTTON_WIDTH * 2) - AUDIO_CHOOSER_LABEL_WIDTH
AUDIO_CHOOSER_WINDOW_WIDTH = CHOOSER_FILE_LIST_X + CHOOSER_FILE_LIST_WIDTH + 8 + (CHOOSER_BUTTON_WIDTH * 2) + 4 + (BORDER_THICKNESS * 2)
AUDIO_CHOOSER_WINDOW_HEIGHT = CHOOSER_FILE_LIST_Y + CHOOSER_FILE_LIST_HEIGHT + 10 + CHOOSER_BUTTON_HEIGHT + 8 + (BORDER_THICKNESS * 2)
AUDIO_CHOOSER_X = ((WINDOW_WIDTH - AUDIO_CHOOSER_WINDOW_WIDTH) / 2)
AUDIO_CHOOSER_Y = ((WINDOW_HEIGHT - AUDIO_CHOOSER_WINDOW_HEIGHT) / 2)
def initialize(anim_id, anim)
@anim_id = anim_id
@anim = anim
@pbs_path = anim[:pbs_path].dup
@pbs_path = anim[:pbs_path]
@quit = false
# Viewports
@viewport = Viewport.new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
@@ -85,13 +99,10 @@ class AnimationEditor
[:commands_pane, :se_pane, :particle_pane, :keyframe_pane].each do |pane|
@components[pane] = UIControls::ControlsContainer.new(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT)
end
# TODO: Make more side panes for:
# - colour/tone editor (accessed from @components[:commands_pane] via
# a button; has Apply/Cancel buttons to only apply all its values at
# the end of editing them, although canvas will be updated in real
# time to show the changes)
# - effects particle properties (depends on keyframe; for screen
# shake, etc.)
# TODO: Make a side pane for colour/tone editor (accessed from
# @components[:commands_pane] via a button; has Apply/Cancel buttons
# to retain/undo all its changes, although changes are made normally
# while in it and canvas is updated accordingly.
# Timeline/particle list
@components[:particle_list] = AnimationEditor::ParticleList.new(
PARTICLE_LIST_X, PARTICLE_LIST_Y, PARTICLE_LIST_WIDTH, PARTICLE_LIST_HEIGHT, @viewport
@@ -99,17 +110,25 @@ class AnimationEditor
@components[:particle_list].set_interactive_rects
# Animation properties pop-up window
@components[:animation_properties] = UIControls::ControlsContainer.new(
ANIM_PROPERTIES_X + 4, ANIM_PROPERTIES_Y + 4, ANIM_PROPERTIES_WIDTH - 8, ANIM_PROPERTIES_HEIGHT - 8
ANIM_PROPERTIES_X + BORDER_THICKNESS + 4, ANIM_PROPERTIES_Y + BORDER_THICKNESS,
ANIM_PROPERTIES_WIDTH - ((BORDER_THICKNESS + 4) * 2), ANIM_PROPERTIES_HEIGHT - (BORDER_THICKNESS * 2)
)
@components[:animation_properties].viewport.z = @pop_up_viewport.z + 1
@components[:animation_properties].label_offset_x = 170
# Graphic chooser pop-up window
@components[:graphic_chooser] = UIControls::ControlsContainer.new(
GRAPHIC_CHOOSER_X + BORDER_THICKNESS, GRAPHIC_CHOOSER_Y + BORDER_THICKNESS,
GRAPHIC_CHOOSER_WINDOW_WIDTH - (BORDER_THICKNESS * 2), GRAPHIC_CHOOSER_WINDOW_HEIGHT - (BORDER_THICKNESS * 2)
)
@components[:graphic_chooser].viewport.z = @pop_up_viewport.z + 1
# Audio chooser pop-up window
@components[:audio_chooser] = UIControls::ControlsContainer.new(
AUDIO_CHOOSER_X + BORDER_THICKNESS, AUDIO_CHOOSER_Y + BORDER_THICKNESS,
AUDIO_CHOOSER_WINDOW_WIDTH - (BORDER_THICKNESS * 2), AUDIO_CHOOSER_WINDOW_HEIGHT - (BORDER_THICKNESS * 2)
)
@components[:audio_chooser].viewport.z = @pop_up_viewport.z + 1
@captured = nil
set_menu_bar_contents
set_canvas_contents
set_play_controls_contents
set_side_panes_contents
set_particle_list_contents
set_animation_properties_contents
set_components_contents
refresh
end
@@ -121,6 +140,7 @@ class AnimationEditor
@components.clear
@viewport.dispose
@canvas_viewport.dispose
@pop_up_viewport.dispose
end
def keyframe
@@ -178,8 +198,12 @@ class AnimationEditor
# sprite, make this instead a choice of front/back/same as the main sprite/
# opposite of the main sprite. Probably need two controls in the same space
# and refresh_component(:commands_pane) makes the appropriate one visible.
commands_pane.add_labelled_number_text_box(:x, _INTL("X"), -128, CANVAS_WIDTH + 128, 64)
commands_pane.add_labelled_number_text_box(:y, _INTL("Y"), -128, CANVAS_HEIGHT + 128, 96)
commands_pane.add_labelled_number_text_box(:x, _INTL("X"), -(CANVAS_WIDTH + 128), CANVAS_WIDTH + 128, 0)
commands_pane.add_labelled_number_text_box(:y, _INTL("Y"), -(CANVAS_WIDTH + 128), CANVAS_HEIGHT + 128, 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
# sprite. Will need two controls in the same space.
commands_pane.add_labelled_number_text_box(:frame, _INTL("Frame"), 0, 99, 0)
commands_pane.add_labelled_checkbox(:visible, _INTL("Visible"), true)
commands_pane.add_labelled_number_slider(:opacity, _INTL("Opacity"), 0, 255, 255)
commands_pane.add_labelled_number_text_box(:zoom_x, _INTL("Zoom X"), 0, 1000, 100)
@@ -273,12 +297,14 @@ class AnimationEditor
# Create "opp" variant
anim_properties.add_labelled_checkbox(:opp_variant, _INTL("User is opposing?"), false)
# Create move control
# TODO: Also make a TextBox here for common animations, and toggle these two
# controls' visibility depending on :type?
# TODO: Instead of having the :common_anim TextBox control, make this a
# TextBoxDropdownList control instead. Make it allow custom text
# as well as any option in the list. Its options will be changed
# depending on the animation's type. Also have a list of existing
# Common animation names for it.
move_list = []
GameData::Move.each { |m| move_list.push([m.id.to_s, m.name]) }
move_list.sort! { |a, b| a[1] <=> b[1] }
# TODO: Make this a TextBoxDropdownList instead.
anim_properties.add_labelled_dropdown_list(:move, _INTL("Move"), move_list.to_h, move_list[0][0])
move_ctrl = anim_properties.get_control(:move)
move_ctrl.max_rows = 16
@@ -297,11 +323,71 @@ class AnimationEditor
# TODO: List, TextBox and some Buttons to add/delete.
# Create "usable in battle" control
anim_properties.add_labelled_checkbox(:usable, _INTL("Can be used in battle?"), true)
# TODO: "Play if target is on the same side as the user".
anim_properties.add_button(:close, _INTL("Close"))
anim_properties.visible = false
end
def set_graphic_chooser_contents
graphic_chooser = @components[:graphic_chooser]
graphic_chooser.add_header_label(:header, _INTL("Choose a file"))
# List of files
list = UIControls::List.new(CHOOSER_FILE_LIST_WIDTH, CHOOSER_FILE_LIST_HEIGHT, graphic_chooser.viewport, [])
graphic_chooser.add_control_at(:list, list, CHOOSER_FILE_LIST_X, CHOOSER_FILE_LIST_Y)
# Buttons
[[:ok, _INTL("OK")], [:cancel, _INTL("Cancel")]].each_with_index do |option, i|
btn = UIControls::Button.new(CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, graphic_chooser.viewport, option[1])
btn.set_fixed_size
graphic_chooser.add_control_at(option[0], btn,
CHOOSER_FILE_LIST_X + (CHOOSER_BUTTON_WIDTH * i),
CHOOSER_FILE_LIST_Y + CHOOSER_FILE_LIST_HEIGHT + 4)
end
graphic_chooser.visible = false
end
def set_audio_chooser_contents
audio_chooser = @components[:audio_chooser]
audio_chooser.add_header_label(:header, _INTL("Choose a file"))
# List of files
list = UIControls::List.new(CHOOSER_FILE_LIST_WIDTH, CHOOSER_FILE_LIST_HEIGHT, audio_chooser.viewport, [])
audio_chooser.add_control_at(:list, list, CHOOSER_FILE_LIST_X, CHOOSER_FILE_LIST_Y)
# Buttons
[[:ok, _INTL("OK")], [:cancel, _INTL("Cancel")]].each_with_index do |option, i|
btn = UIControls::Button.new(CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, audio_chooser.viewport, option[1])
btn.set_fixed_size
audio_chooser.add_control_at(option[0], btn,
CHOOSER_FILE_LIST_X + (CHOOSER_BUTTON_WIDTH * i),
CHOOSER_FILE_LIST_Y + CHOOSER_FILE_LIST_HEIGHT + 4)
end
# Volume and pitch sliders
[[:volume, _INTL("Volume"), 0, 100], [:pitch, _INTL("Pitch"), 0, 200]].each_with_index do |option, i|
label = UIControls::Label.new(AUDIO_CHOOSER_LABEL_WIDTH, 28, audio_chooser.viewport, option[1])
audio_chooser.add_control_at((option[0].to_s + "_label").to_sym, label,
list.x + list.width + 8, list.y + (28 * i))
slider = UIControls::NumberSlider.new(AUDIO_CHOOSER_SLIDER_WIDTH, 28, audio_chooser.viewport, option[2], option[3], 100)
audio_chooser.add_control_at(option[0], slider, label.x + label.width, label.y)
end
# Playback buttons
[[:play, _INTL("Play")], [:stop, _INTL("Stop")]].each_with_index do |option, i|
btn = UIControls::Button.new(CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, audio_chooser.viewport, option[1])
btn.set_fixed_size
audio_chooser.add_control_at(option[0], btn,
list.x + list.width + 8 + (CHOOSER_BUTTON_WIDTH * i),
list.y + (28 * 2))
end
audio_chooser.visible = false
end
def set_components_contents
set_menu_bar_contents
set_canvas_contents
set_play_controls_contents
set_side_panes_contents
set_particle_list_contents
set_animation_properties_contents
set_graphic_chooser_contents
set_audio_chooser_contents
end
#-----------------------------------------------------------------------------
def save
@@ -320,19 +406,17 @@ class AnimationEditor
#-----------------------------------------------------------------------------
def draw_editor_background
draw_big_outline = lambda do |bitmap, x, y, width, height|
BORDER_THICKNESS.times do |i|
col = (i.even?) ? Color.white : Color.black
bitmap.outline_rect(x - i - 1, y - i - 1, width + (i * 2) + 2, height + (i * 2) + 2, col)
end
end
# Fill the whole screen with white
@screen_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.white)
# Outline around elements
draw_big_outline.call(@screen_bitmap.bitmap, CANVAS_X, CANVAS_Y, CANVAS_WIDTH, CANVAS_HEIGHT)
draw_big_outline.call(@screen_bitmap.bitmap, PLAY_CONTROLS_X, PLAY_CONTROLS_Y, PLAY_CONTROLS_WIDTH, PLAY_CONTROLS_HEIGHT)
draw_big_outline.call(@screen_bitmap.bitmap, SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT)
draw_big_outline.call(@screen_bitmap.bitmap, PARTICLE_LIST_X, PARTICLE_LIST_Y, PARTICLE_LIST_WIDTH, PARTICLE_LIST_HEIGHT)
@screen_bitmap.bitmap.border_rect(CANVAS_X, CANVAS_Y, CANVAS_WIDTH, CANVAS_HEIGHT,
BORDER_THICKNESS, Color.white, Color.black)
@screen_bitmap.bitmap.border_rect(PLAY_CONTROLS_X, PLAY_CONTROLS_Y, PLAY_CONTROLS_WIDTH, PLAY_CONTROLS_HEIGHT,
BORDER_THICKNESS, Color.white, Color.black)
@screen_bitmap.bitmap.border_rect(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT,
BORDER_THICKNESS, Color.white, Color.black)
@screen_bitmap.bitmap.border_rect(PARTICLE_LIST_X, PARTICLE_LIST_Y, PARTICLE_LIST_WIDTH, PARTICLE_LIST_HEIGHT,
BORDER_THICKNESS, Color.white, Color.black)
# Draw box around SE list box in side pane
@se_list_box_bitmap.bitmap.outline_rect(SIDE_PANE_X + 3, SIDE_PANE_Y + 24 + 4,
SIDE_PANE_WIDTH - 6, (5 * UIControls::List::ROW_HEIGHT) + 4, Color.black)
@@ -365,14 +449,14 @@ class AnimationEditor
case component_sym
when :commands_pane
new_vals = AnimationEditor::ParticleDataHelper.get_all_keyframe_particle_values(@anim[:particles][particle_index], keyframe)
# TODO: Need to do something special for :color, :tone and :frame which
# all have button controls.
component.controls.each do |ctrl|
next if !new_vals.include?(ctrl[0])
ctrl[1].value = new_vals[ctrl[0]][0] if ctrl[1].respond_to?("value=")
# TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated,
# which should be indicated somehow in ctrl[1].
end
# TODO: component.get_control(:frame).disable if the particle's graphic is
# not a spritesheet or is "USER"/"TARGET"/etc. (enable otherwise).
when :se_pane
se_particle = @anim[:particles].select { |p| p[:name] == "SE" }[0]
kyfrm = keyframe
@@ -428,18 +512,20 @@ class AnimationEditor
component.get_control(:graphic).enable
component.get_control(:focus).enable
end
# TODO: Set the possible focus options depending on whether the animation
# has a target/user.
when :animation_properties
case @anim[:type]
when :move, :opp_move
component.get_control(:move_label).text = _INTL("Move")
component.get_control(:move).visible = true
component.get_control(:move).value = @anim[:move].dup
component.get_control(:move).value = @anim[:move]
component.get_control(:common_anim).visible = false
when :common, :opp_common
component.get_control(:move_label).text = _INTL("Common animation")
component.get_control(:move).visible = false
component.get_control(:common_anim).visible = true
component.get_control(:common_anim).value = @anim[:move].dup
component.get_control(:common_anim).value = @anim[:move]
end
# TODO: Maybe other things as well?
end
@@ -458,8 +544,6 @@ class AnimationEditor
#-----------------------------------------------------------------------------
# TODO: Every component that contains a button, etc. should respond to
# "values", which returns the changed elements.
def apply_changed_value(component_sym, property, value)
case component_sym
when :menu_bar
@@ -544,7 +628,7 @@ class AnimationEditor
if @anim[:particles][p_index][:graphic] != new_file
@anim[:particles][p_index][:graphic] = new_file
refresh_component(:particle_pane)
# TODO: refresh_component(:canvas)
refresh_component(:canvas)
end
else
particle = @anim[:particles][particle_index]
@@ -572,7 +656,7 @@ class AnimationEditor
when :common_anim
@anim[:move] = value
when :pbs_path
txt = value.dup.gsub!(/\.txt$/, "")
txt = value.gsub!(/\.txt$/, "")
@anim[property] = txt
when :has_target
@anim[:no_target] = !value

View File

@@ -2,20 +2,6 @@
#
#===============================================================================
class AnimationEditor
MESSAGE_BOX_WIDTH = WINDOW_WIDTH * 3 / 4
MESSAGE_BOX_HEIGHT = 160
MESSAGE_BOX_BUTTON_WIDTH = 150
MESSAGE_BOX_BUTTON_HEIGHT = 32
MESSAGE_BOX_SPACING = 16
GRAPHIC_CHOOSER_BUTTON_WIDTH = 150
GRAPHIC_CHOOSER_BUTTON_HEIGHT = MESSAGE_BOX_BUTTON_HEIGHT
GRAPHIC_CHOOSER_FILE_LIST_WIDTH = GRAPHIC_CHOOSER_BUTTON_WIDTH * 2
GRAPHIC_CHOOSER_FILE_LIST_HEIGHT = 15 * UIControls::List::ROW_HEIGHT
GRAPHIC_CHOOSER_PREVIEW_SIZE = 320
GRAPHIC_CHOOSER_WINDOW_WIDTH = GRAPHIC_CHOOSER_FILE_LIST_WIDTH + GRAPHIC_CHOOSER_PREVIEW_SIZE + (MESSAGE_BOX_SPACING * 2) + 8
GRAPHIC_CHOOSER_WINDOW_HEIGHT = GRAPHIC_CHOOSER_FILE_LIST_HEIGHT + GRAPHIC_CHOOSER_BUTTON_HEIGHT + 24 + (MESSAGE_BOX_SPACING * 2) + 2
def create_pop_up_window(width, height)
ret = BitmapSprite.new(width, height, @pop_up_viewport)
ret.x = (WINDOW_WIDTH - width) / 2
@@ -24,10 +10,9 @@ class AnimationEditor
ret.bitmap.font.color = Color.black
ret.bitmap.font.size = 18
# Draw message box border
BORDER_THICKNESS.times do |i|
col = (i.even?) ? Color.black : Color.white
ret.bitmap.outline_rect(i, i, ret.width - (i * 2), ret.height - (i * 2), col)
end
ret.bitmap.border_rect(BORDER_THICKNESS, BORDER_THICKNESS,
ret.width - (BORDER_THICKNESS * 2), ret.height - (BORDER_THICKNESS * 2),
BORDER_THICKNESS, Color.white, Color.black)
# Fill message box with white
ret.bitmap.fill_rect(BORDER_THICKNESS, BORDER_THICKNESS,
ret.width - (BORDER_THICKNESS * 2),
@@ -94,66 +79,64 @@ class AnimationEditor
#-----------------------------------------------------------------------------
# Generates a list of all files in the given folder which have a file
# extension that matches one in exts. Removes any files from the list whose
# filename is the same as one in prepends (case insensitive), and then adds
# all the files in prepends to the start of the list.
def get_all_files_in_folder_and_prepend(folder, exts, prepends)
ret = []
Dir.chdir(folder) do
exts.each do |type|
Dir.glob(type) { |f| ret.push([File.basename(f, ".*"), f]) }
end
end
ret.delete_if { |f| prepends.any? { |add| add[0] == f[0].upcase } }
ret.sort! { |a, b| a[0].downcase <=> b[0].downcase }
ret.prepend(*prepends)
return ret
end
def choose_graphic_file(selected)
selected ||= ""
sprite_folder = "Graphics/Battle animations/"
# Show pop-up window
@pop_up_bg_bitmap.visible = true
bg_bitmap = create_pop_up_window(GRAPHIC_CHOOSER_WINDOW_WIDTH, GRAPHIC_CHOOSER_WINDOW_HEIGHT)
graphic_chooser = @components[:graphic_chooser]
graphic_chooser.visible = true
# Draw box around list control
list = graphic_chooser.get_control(:list)
bg_bitmap.bitmap.outline_rect(list.x + BORDER_THICKNESS - 2, list.y + BORDER_THICKNESS - 2,
list.width + 4, list.height + 4, Color.black)
# Get a list of files
files = []
Dir.chdir(sprite_folder) do
Dir.glob("*.png") { |f| files.push([File.basename(f, ".*"), f]) }
Dir.glob("*.jpg") { |f| files.push([File.basename(f, ".*"), f]) }
Dir.glob("*.jpeg") { |f| files.push([File.basename(f, ".*"), f]) }
end
files.delete_if { |f| ["USER", "USER_OPP", "USER_FRONT", "USER_BACK",
"TARGET", "TARGET_OPP", "TARGET_FRONT",
"TARGET_BACK"].include?(f[0].upcase) }
files.sort! { |a, b| a[0].downcase <=> b[0].downcase }
files.prepend(["USER", _INTL("[[User's sprite]]")],
["USER_OPP", _INTL("[[User's other side sprite]]")],
["USER_FRONT", _INTL("[[User's front sprite]]")],
["USER_BACK", _INTL("[[User's back sprite]]")],
["TARGET", _INTL("[[Target's sprite]]")],
["TARGET_OPP", _INTL("[[Target's other side sprite]]")],
["TARGET_FRONT", _INTL("[[Target's front sprite]]")],
["TARGET_BACK", _INTL("[[Target's back sprite]]")])
files = get_all_files_in_folder_and_prepend(
sprite_folder, ["*.png", "*.jpg", "*.jpeg"],
[["USER", _INTL("[[User's sprite]]")],
["USER_OPP", _INTL("[[User's other side sprite]]")],
["USER_FRONT", _INTL("[[User's front sprite]]")],
["USER_BACK", _INTL("[[User's back sprite]]")],
["TARGET", _INTL("[[Target's sprite]]")],
["TARGET_OPP", _INTL("[[Target's other side sprite]]")],
["TARGET_FRONT", _INTL("[[Target's front sprite]]")],
["TARGET_BACK", _INTL("[[Target's back sprite]]")]]
)
idx = 0
files.each_with_index do |f, i|
next if f[0] != selected
idx = i
break
end
# Show pop-up window
@pop_up_bg_bitmap.visible = true
bg_bitmap = create_pop_up_window(GRAPHIC_CHOOSER_WINDOW_WIDTH, GRAPHIC_CHOOSER_WINDOW_HEIGHT)
text = _INTL("Choose a file...")
text_size = bg_bitmap.bitmap.text_size(text)
bg_bitmap.bitmap.draw_text(MESSAGE_BOX_SPACING, 11, bg_bitmap.width, text_size.height, text, 0)
# Create list of files
list = UIControls::List.new(GRAPHIC_CHOOSER_FILE_LIST_WIDTH, GRAPHIC_CHOOSER_FILE_LIST_HEIGHT, @pop_up_viewport, files)
list.x = bg_bitmap.x + MESSAGE_BOX_SPACING
list.y = bg_bitmap.y + MESSAGE_BOX_SPACING + 24
# Set control values
list.values = files
list.selected = idx
list.set_interactive_rects
list.repaint
bg_bitmap.bitmap.outline_rect(MESSAGE_BOX_SPACING - 2, MESSAGE_BOX_SPACING + 24 - 2,
GRAPHIC_CHOOSER_FILE_LIST_WIDTH + 4, GRAPHIC_CHOOSER_FILE_LIST_HEIGHT + 4, Color.black)
# Create buttons
buttons = []
[[:ok, _INTL("OK")], [:cancel, _INTL("Cancel")]].each_with_index do |option, i|
btn = UIControls::Button.new(GRAPHIC_CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, @pop_up_viewport, option[1])
btn.x = list.x + (GRAPHIC_CHOOSER_BUTTON_WIDTH * i)
btn.y = list.y + list.height + 2
btn.set_fixed_size
btn.set_interactive_rects
buttons.push([option[0], btn])
end
# Create sprite preview
bg_bitmap.bitmap.outline_rect(MESSAGE_BOX_SPACING + list.width + 6, MESSAGE_BOX_SPACING + 24 - 2,
bg_bitmap.bitmap.outline_rect(BORDER_THICKNESS + list.x + list.width + 10 - 2,
BORDER_THICKNESS + list.y - 2,
GRAPHIC_CHOOSER_PREVIEW_SIZE + 4, GRAPHIC_CHOOSER_PREVIEW_SIZE + 4,
Color.black)
preview_sprite = Sprite.new(@pop_up_viewport)
preview_sprite.x = list.x + list.width + 8 + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2)
preview_sprite.y = list.y + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2)
preview_sprite.x = graphic_chooser.x + list.x + list.width + 10 + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2)
preview_sprite.y = graphic_chooser.y + list.y + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2)
preview_bitmap = nil
set_preview_graphic = lambda do |sprite, filename|
preview_bitmap&.dispose
@@ -166,7 +149,7 @@ class AnimationEditor
else
preview_bitmap = AnimatedBitmap.new(sprite_folder + filename)
end
bg_bitmap.bitmap.fill_rect(MESSAGE_BOX_SPACING + list.width + 8, MESSAGE_BOX_SPACING + 24,
bg_bitmap.bitmap.fill_rect(BORDER_THICKNESS + list.x + list.width + 10, BORDER_THICKNESS + list.y,
GRAPHIC_CHOOSER_PREVIEW_SIZE, GRAPHIC_CHOOSER_PREVIEW_SIZE,
Color.white)
next if !preview_bitmap
@@ -176,52 +159,42 @@ class AnimationEditor
sprite.zoom_x = sprite.zoom_y = zoom
sprite.ox = sprite.width / 2
sprite.oy = sprite.height / 2
bg_bitmap.bitmap.fill_rect(MESSAGE_BOX_SPACING + list.width + 8 + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2) - (sprite.width * sprite.zoom_x / 2),
MESSAGE_BOX_SPACING + 24 + (GRAPHIC_CHOOSER_PREVIEW_SIZE / 2) - (sprite.height * sprite.zoom_y / 2),
bg_bitmap.bitmap.fill_rect(BORDER_THICKNESS + sprite.x - graphic_chooser.x - (sprite.width * sprite.zoom_x / 2),
BORDER_THICKNESS + sprite.y - graphic_chooser.y - (sprite.height * sprite.zoom_y / 2),
sprite.width * sprite.zoom_x, sprite.height * sprite.zoom_y,
Color.magenta)
end
set_preview_graphic.call(preview_sprite, list.value)
# Interaction loop
ret = nil
captured = nil
loop do
Graphics.update
Input.update
if captured
captured.update
captured = nil if !captured.busy?
else
list.update
captured = list if list.busy?
buttons.each do |btn|
btn[1].update
captured = btn[1] if btn[1].busy?
graphic_chooser.update
if graphic_chooser.changed?
graphic_chooser.values.each_pair do |ctrl, value|
case ctrl
when :ok
ret = list.value
when :cancel
ret = selected
when :list
set_preview_graphic.call(preview_sprite, list.value)
end
graphic_chooser.clear_changed
end
ret = selected if !graphic_chooser.busy? && Input.trigger?(Input::BACK)
break if ret
graphic_chooser.repaint
end
if list.changed?
set_preview_graphic.call(preview_sprite, list.value)
list.clear_changed
end
buttons.each do |btn|
next if !btn[1].changed?
ret = list.value if btn[0] == :ok
ret = selected if btn[0] == :cancel
break
end
ret = selected if Input.trigger?(Input::BACK)
break if ret
list.repaint
buttons.each { |btn| btn[1].repaint }
end
# Dispose and return
list.dispose
buttons.each { |btn| btn[1].dispose }
buttons.clear
bg_bitmap.dispose
preview_sprite.dispose
preview_bitmap&.dispose
@pop_up_bg_bitmap.visible = false
graphic_chooser.clear_changed
graphic_chooser.visible = false
return ret
end
@@ -229,119 +202,75 @@ class AnimationEditor
def choose_audio_file(selected, volume = 100, pitch = 100)
selected ||= ""
sprite_folder = "Audio/SE/Anim/"
audio_folder = "Audio/SE/Anim/"
# Show pop-up window
@pop_up_bg_bitmap.visible = true
bg_bitmap = create_pop_up_window(AUDIO_CHOOSER_WINDOW_WIDTH, AUDIO_CHOOSER_WINDOW_HEIGHT)
audio_chooser = @components[:audio_chooser]
audio_chooser.visible = true
# Draw box around list control
list = audio_chooser.get_control(:list)
bg_bitmap.bitmap.outline_rect(list.x + BORDER_THICKNESS - 2, list.y + BORDER_THICKNESS - 2,
list.width + 4, list.height + 4, Color.black)
# Get a list of files
files = []
Dir.chdir(sprite_folder) do
Dir.glob("*.wav") { |f| files.push([File.basename(f, ".*"), f]) }
Dir.glob("*.ogg") { |f| files.push([File.basename(f, ".*"), f]) }
Dir.glob("*.mp3") { |f| files.push([File.basename(f, ".*"), f]) }
Dir.glob("*.wma") { |f| files.push([File.basename(f, ".*"), f]) }
end
files.delete_if { |f| ["USER", "TARGET"].include?(f[0].upcase) }
files.sort! { |a, b| a[0].downcase <=> b[0].downcase }
files.prepend(["USER", _INTL("[[User's cry]]")],
["TARGET", _INTL("[[Target's cry]]")])
files = get_all_files_in_folder_and_prepend(
audio_folder, ["*.wav", "*.ogg", "*.mp3", "*.wma"],
[["USER", _INTL("[[User's cry]]")],
["TARGET", _INTL("[[Target's cry]]")]]
)
idx = 0
files.each_with_index do |f, i|
next if f[0] != selected
idx = i
break
end
# Show pop-up window
@pop_up_bg_bitmap.visible = true
bg_bitmap = create_pop_up_window(GRAPHIC_CHOOSER_WINDOW_WIDTH - 24, GRAPHIC_CHOOSER_WINDOW_HEIGHT)
text = _INTL("Choose a file...")
text_size = bg_bitmap.bitmap.text_size(text)
bg_bitmap.bitmap.draw_text(MESSAGE_BOX_SPACING, 11, bg_bitmap.width, text_size.height, text, 0)
# Create list of files
list = UIControls::List.new(GRAPHIC_CHOOSER_FILE_LIST_WIDTH, GRAPHIC_CHOOSER_FILE_LIST_HEIGHT, @pop_up_viewport, files)
list.x = bg_bitmap.x + MESSAGE_BOX_SPACING
list.y = bg_bitmap.y + MESSAGE_BOX_SPACING + 24
# Set control values
list.values = files
list.selected = idx
list.set_interactive_rects
list.repaint
bg_bitmap.bitmap.outline_rect(MESSAGE_BOX_SPACING - 2, MESSAGE_BOX_SPACING + 24 - 2,
GRAPHIC_CHOOSER_FILE_LIST_WIDTH + 4, GRAPHIC_CHOOSER_FILE_LIST_HEIGHT + 4, Color.black)
# Create buttons
buttons = []
[[:ok, _INTL("OK")], [:cancel, _INTL("Cancel")]].each_with_index do |option, i|
btn = UIControls::Button.new(GRAPHIC_CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, @pop_up_viewport, option[1])
btn.x = list.x + (GRAPHIC_CHOOSER_BUTTON_WIDTH * i)
btn.y = list.y + list.height + 2
btn.set_fixed_size
btn.set_interactive_rects
buttons.push([option[0], btn])
end
# Create audio player controls
[[:volume, _INTL("Volume"), 0, 100], [:pitch, _INTL("Pitch"), 0, 200]].each_with_index do |option, i|
label = UIControls::Label.new(90, 28, @pop_up_viewport, option[1])
label.x = list.x + list.width + 8
label.y = list.y + (28 * i)
label.set_interactive_rects
buttons.push([(option[0].to_s + "_label").to_sym, label])
slider = UIControls::NumberSlider.new(250, 28, @pop_up_viewport, option[2], option[3], (i == 0 ? volume : pitch))
slider.x = list.x + list.width + 8 + label.width
slider.y = list.y + (28 * i)
slider.set_interactive_rects
buttons.push([option[0], slider])
end
[[:play, _INTL("Play")], [:stop, _INTL("Stop")]].each_with_index do |option, i|
btn = UIControls::Button.new(GRAPHIC_CHOOSER_BUTTON_WIDTH, MESSAGE_BOX_BUTTON_HEIGHT, @pop_up_viewport, option[1])
btn.x = list.x + list.width + 8 + (GRAPHIC_CHOOSER_BUTTON_WIDTH * i)
btn.y = list.y + (28 * 2)
btn.set_fixed_size
btn.set_interactive_rects
buttons.push([option[0], btn])
end
audio_chooser.get_control(:volume).value = volume
audio_chooser.get_control(:pitch).value = pitch
# Interaction loop
ret = nil
captured = nil
cancel = false
loop do
Graphics.update
Input.update
if captured
captured.update
captured = nil if !captured.busy?
else
list.update
captured = list if list.busy?
buttons.each do |btn|
btn[1].update
captured = btn[1] if btn[1].busy?
audio_chooser.update
if audio_chooser.changed?
audio_chooser.values.each_pair do |ctrl, value|
case ctrl
when :ok
ret = list.value
when :cancel
ret = selected
cancel = true
when :play
vol = audio_chooser.get_control(:volume).value
ptch = audio_chooser.get_control(:pitch).value
# TODO: Play appropriate things if a cry is selected. See which
# battlers are defined in the editor's settings, and use their
# cries.
pbSEPlay(RPG::AudioFile.new("Anim/" + list.value, vol, ptch))
when :stop
pbSEStop
end
audio_chooser.clear_changed
end
end
buttons.each do |btn|
next if !btn[1].changed?
case btn[0]
when :ok
ret = list.value
when :cancel
if !audio_chooser.busy? && Input.trigger?(Input::BACK)
ret = selected
when :play
vol = buttons.select { |b| b[0] == :volume }[0][1].value
ptch = buttons.select { |b| b[0] == :pitch }[0][1].value
# TODO: Play appropriate things if a cry is selected.
pbSEPlay(RPG::AudioFile.new("Anim/" + list.value, vol, ptch))
when :stop
pbSEStop
cance = true
end
btn[1].clear_changed
break
break if ret
audio_chooser.repaint
end
ret = selected if Input.trigger?(Input::BACK)
break if ret
list.repaint
buttons.each { |btn| btn[1].repaint }
end
vol = buttons.select { |b| b[0] == :volume }[0][1].value
ptch = buttons.select { |b| b[0] == :pitch }[0][1].value
vol = (cancel) ? volume : audio_chooser.get_control(:volume).value
ptch = (cancel) ? pitch : audio_chooser.get_control(:pitch).value
# Dispose and return
list.dispose
buttons.each { |btn| btn[1].dispose }
buttons.clear
bg_bitmap.dispose
@pop_up_bg_bitmap.visible = false
audio_chooser.clear_changed
audio_chooser.visible = false
return [ret, vol, ptch]
end
@@ -352,9 +281,9 @@ class AnimationEditor
@pop_up_bg_bitmap.visible = true
bg_bitmap = create_pop_up_window(ANIM_PROPERTIES_WIDTH, ANIM_PROPERTIES_HEIGHT)
# TODO: Draw box around list control(s), i.e. flags.
@components[:animation_properties].visible = true
# Set control values
anim_properties = @components[:animation_properties]
anim_properties.visible = true
# Set control values
case @anim[:type]
when :move, :opp_move
anim_properties.get_control(:type).value = :move
@@ -371,7 +300,6 @@ class AnimationEditor
refresh_component(:animation_properties) # This sets the :move control's value
# Interaction loop
ret = nil
captured = nil
loop do
Graphics.update
Input.update

View File

@@ -66,11 +66,11 @@ class AnimationEditor::AnimationSelector
btn.set_fixed_size
@components.add_control_at(val[0], btn, TYPE_BUTTONS_X, TYPE_BUTTONS_Y + (i * TYPE_BUTTON_HEIGHT))
end
# TODO: Filter text box for :moves_list's contents. Applies the filter upon
# every change to the text box's value. Perhaps it should only do so
# after 0.5 seconds of non-typing. What exactly should the filter be
# applied to? Animation's name, move's name (if there is one), what
# else?
# TODO: Add filter text box for :moves_list's contents. Applies the filter
# upon every change to the text box's value. Perhaps it should only do
# so after 0.5 seconds of non-typing. What exactly should the filter
# be applied to? Animation's name, move's name (if there is one), what
# else? Flags?
# Moves list label
label = UIControls::Label.new(MOVES_LIST_WIDTH, TYPE_BUTTON_HEIGHT, @viewport, _INTL("Moves"))
label.header = true
@@ -190,7 +190,11 @@ class AnimationEditor::AnimationSelector
@quit = true
return # Don't need to refresh the screen
when :new
# TODO: New animation.
# TODO: New animation. Create a new animation hash with some default
# contents, go into the edit screen and immediately open the
# animation properties pop-up window. Use the first available ID
# number from GameData::Animation for it. Don't register the
# animation hash here, though.
when :moves
@animation_type = 0
@components.get_control(:moves_list).selected = -1
@@ -204,19 +208,17 @@ class AnimationEditor::AnimationSelector
if anim_id
screen = AnimationEditor.new(anim_id, GameData::Animation.get(anim_id).clone_as_hash)
screen.run
# TODO: Might want to select whichever options in each list get to
# the animation with ID anim_id.
generate_lists
end
when :copy
anim_id = selected_animation_id
if anim_id
# TODO: Copy animation.
# TODO: Copy animation. Append "(copy)" to its name.
end
when :delete
anim_id = selected_animation_id
if anim_id
# TODO: Delete animation.
# TODO: Delete animation. Ask the user if they're sure.
end
end
refresh

View File

@@ -146,7 +146,6 @@ module AnimationEditor::ParticleDataHelper
if cmd[1] > 0 # MoveXYZ
dur = cmd[1]
dur *= -1 if cmd[2] < val
# TODO: Support multiple interpolation types here (will be cmd[3]).
ret[cmd[0]] = [dur, cmd[3] || :linear]
ret[cmd[0] + cmd[1]] = 0
else # SetXYZ

View File

@@ -7,7 +7,6 @@ class AnimationEditor::Canvas < Sprite
def initialize(viewport)
super
@bg_val = ""
# TODO: Add a second bg sprite for screen shake purposes.
player_base_pos = Battle::Scene.pbBattlerPosition(0)
@player_base = IconSprite.new(*player_base_pos, viewport)
@player_base.z = 1
@@ -28,8 +27,9 @@ class AnimationEditor::Canvas < Sprite
def bg_name=(val)
return if @bg_name == val
@bg_name = val
# TODO: Come up with a better way to define the base filenames, based on
# which files actually exist.
# TODO: Make the choice of background graphics match the in-battle one in
# def pbCreateBackdropSprites. Ideally make that method a class method
# so the canvas can use it rather than duplicate it.
self.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", @bg_name + "_bg")
@player_base.setBitmap("Graphics/Battlebacks/" + @bg_name + "_base0")
@player_base.ox = @player_base.bitmap.width / 2

View File

@@ -2,8 +2,6 @@
# TODO
#===============================================================================
class AnimationEditor::PlayControls < UIControls::BaseControl
TEXT_OFFSET_Y = 5
def initialize(x, y, width, height, viewport)
super(width, height, viewport)
self.x = x

View File

@@ -1,8 +1,5 @@
#===============================================================================
# TODO: Would be nice to make command sprites wider than their viewport and
# change @commands_viewport's ox to @left_pos, similar to how the vertical
# scrollbar works, i.e. every visible @commands_sprites isn't redrawn each
# time the horizontal scrollbar changes.
#
#===============================================================================
class AnimationEditor::ParticleList < UIControls::BaseControl
VIEWPORT_SPACING = 1
@@ -247,8 +244,6 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
#-----------------------------------------------------------------------------
def calculate_duration
# TODO: Refresh lots of things if the duration changed (e.g. SE command
# line).
@duration = AnimationEditor::ParticleDataHelper.get_duration(@particles)
@duration += DURATION_BUFFER
end
@@ -269,7 +264,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
end
def calculate_commands_for_particle(index)
# TODO: Delete everything from @commands that includes index.
@commands.delete_if { |cmd| cmd == index || (cmd.is_a?(Array) && cmd[0] == index) }
overall_commands = []
@particles[index].each_pair do |property, value|
next if !value.is_a?(Array)
@@ -290,8 +285,6 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
# Returns whether the sprites need replacing due to the addition or
# subtraction of one.
def ensure_sprites
# TODO: Check through @particle_list to ensure only ones are shown which
# correspond to something in @particles.
# Go through all @particles to ensure there are sprites for each of them
missing = false
@particles.each_with_index do |particle, index|
@@ -379,7 +372,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
def property_display_name(property)
return {
:frame => _INTL("Graphic frame"),
:frame => _INTL("Frame"),
:blending => _INTL("Blending"),
:flip => _INTL("Flip"),
:x => _INTL("X"),
@@ -438,7 +431,9 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
end
end
# TODO: Add indicator that this is selected (if so).
# 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?
def refresh_particle_list_sprite(index)
spr = @list_sprites[index]
return if !spr

View File

@@ -1,9 +1,5 @@
#===============================================================================
# TODO: Ideally the menu buttons (add_button) will be replaced by a proper
# menu control (basically multiple DropdownList controls where the headers
# have fixed names and, while captured, hovering over a header changes
# which list is displayed). The menu control should go under UI controls
# rather than be unique to the Animation Editor.
#
#===============================================================================
class AnimationEditor::MenuBar < UIControls::ControlsContainer
MENU_BUTTON_WIDTH = 80