# List of functions that exist in the code but don't have a menu option:
# pbCelRotateFlip
# pbFlipAroundPoint

################################################################################
# Controls
################################################################################
require 'zlib'
class Window_Menu < Window_CommandPokemon
  def initialize(commands, x, y)
    tempbitmap = Bitmap.new(32, 32)
    w = 0
    for i in commands
      width = tempbitmap.text_size(i).width
      w = width if w < width
    end
    w += 16 + self.borderX
    super(commands, w)
    h = [commands.length * 32, 480].min
    h += self.borderY
    right = [x + w, 640].min
    bottom = [y + h, 480].min
    left = right - w
    top = bottom - h
    self.x = left
    self.y = top
    self.width = w
    self.height = h
    tempbitmap.dispose
  end

  def hittest
    mousepos = Mouse::getMousePos
    return -1 if !mousepos

    toprow = self.top_row
    for i in toprow...toprow + @item_max
      rc = Rect.new(0, 32 * (i - toprow), self.contents.width, 32)
      rc.x += self.x + self.leftEdge
      rc.y += self.y + self.topEdge
      if rc.contains(mousepos[0], mousepos[1])
        return i
      end
    end
    return -1
  end
end

module ShadowText
  def shadowtext(bitmap, x, y, w, h, t, disabled = false, align = 0)
    width = bitmap.text_size(t).width
    if align == 2
      x += (w - width)
    elsif align == 1
      x += (w / 2) - (width / 2)
    end
    pbDrawShadowText(
      bitmap, x, y, w, h, t,
      disabled ? Color.new(26 * 8, 26 * 8, 25 * 8) : Color.new(12 * 8, 12 * 8, 12 * 8),
      Color.new(26 * 8, 26 * 8, 25 * 8)
    )
  end
end

class UIControl
  include ShadowText
  attr_accessor :bitmap
  attr_accessor :label
  attr_accessor :x
  attr_accessor :y
  attr_accessor :width
  attr_accessor :height
  attr_accessor :changed
  attr_accessor :parent
  attr_accessor :disabled
  attr_accessor :setProc

  def text
    return self.label
  end

  def text=(value)
    self.label = value
  end

  def initialize(label, setblock = nil)
    @setProc = setblock
    @label = label
    @x = 0
    @y = 0
    @width = 0
    @height = 0
    @changed = false
    @disabled = false
    @invalid = true
  end

  def toAbsoluteRect(rc)
    return Rect.new(
      rc.x + self.parentX,
      rc.y + self.parentY,
      rc.width, rc.height
    )
  end

  def parentX
    return 0 if !self.parent
    return self.parent.x + self.parent.leftEdge if self.parent.is_a?(SpriteWindow)
    return self.parent.x + 16 if self.parent.is_a?(Window)

    return self.parent.x
  end

  def parentY
    return 0 if !self.parent
    return self.parent.y + self.parent.topEdge if self.parent.is_a?(SpriteWindow)
    return self.parent.y + 16 if self.parent.is_a?(Window)

    return self.parent.y
  end

  def invalid?
    return @invalid
  end

  def invalidate # Marks that the control must be redrawn to reflect current logic
    @invalid = true
  end

  def update # Updates the logic on the control, invalidating it if necessary
  end

  def refresh # Redraws the control
  end

  def validate # Makes the control no longer invalid
    @invalid = false
  end

  def repaint # Redraws the control only if it is invalid
    if self.invalid?
      self.refresh
      self.validate
    end
  end
end

class Label < UIControl
  def text=(value)
    self.label = value
    refresh
  end

  def refresh
    bitmap = self.bitmap
    bitmap.fill_rect(self.x, self.y, self.width, self.height, Color.new(0, 0, 0, 0))
    size = bitmap.text_size(self.label).width
    shadowtext(bitmap, self.x + 4, self.y, size, self.height, self.label, @disabled)
  end
end

class Button < UIControl
  attr_accessor :label

  def initialize(label, setblock = nil)
    super(label, setblock)
    @captured = false
    @label = label
  end

  def update
    mousepos = Mouse::getMousePos
    self.changed = false
    return if !mousepos

    rect = Rect.new(self.x + 1, self.y + 1, self.width - 2, self.height - 2)
    rect = toAbsoluteRect(rect)
    if Input.triggerex?(Input::LeftMouseKey) && rect.contains(mousepos[0], mousepos[1]) && !@captured
      @captured = true
      self.invalidate
    end
    if Input.releaseex?(Input::LeftMouseKey) && @captured
      self.changed = true if rect.contains(mousepos[0], mousepos[1])
      @captured = false
      self.invalidate
    end
  end

  def refresh
    bitmap = self.bitmap
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    color = Color.new(120, 120, 120)
    bitmap.fill_rect(x + 1, y + 1, width - 2, height - 2, color)
    ret = Rect.new(x + 1, y + 1, width - 2, height - 2)
    if !@captured
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(0, 0, 0, 0))
    else
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(120, 120, 120, 80))
    end
    size = bitmap.text_size(self.label).width
    shadowtext(bitmap, x + 4, y, size, height, self.label, @disabled)
    return ret
  end
end

class Checkbox < Button
  attr_reader :checked

  def curvalue
    return self.checked
  end

  def curvalue=(value)
    self.checked = value
  end

  def checked=(value)
    @checked = value
    invalidate
  end

  def setvalue()
    if @setProc.nil?
      print "something went wrong ", caller
      return
    end
    @setProc.call(curvalue)
  end

  def initialize(label, setblock = nil)
    super(label, setblock)
    @checked = false
  end

  def update
    super
    if self.changed
      @checked = !@checked
      self.invalidate
    end
  end

  def refresh
    bitmap = self.bitmap
    x = self.x
    y = self.y
    width = [self.width, 32].min
    height = [self.height, 32].min
    color = Color.new(120, 120, 120)
    bitmap.fill_rect(x + 2, y + 2, self.width - 4, self.height - 4, Color.new(0, 0, 0, 0))
    bitmap.fill_rect(x + 1, y + 1, width - 2, height - 2, color)
    ret = Rect.new(x + 1, y + 1, width - 2, height - 2)
    if !@captured
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(0, 0, 0, 0))
    else
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(120, 120, 120, 80))
    end
    if self.checked
      shadowtext(bitmap, x, y, 32, 32, "X", @disabled, 1)
    end
    size = bitmap.text_size(self.label).width
    shadowtext(bitmap, x + 36, y, size, height, self.label, @disabled)
    return ret
  end
end

class TextField < UIControl
  attr_accessor :label
  attr_reader :text

  def text=(value)
    @text = value
    self.invalidate
  end

  def initialize(label, text)
    super(label)
    @frame = 0
    @label = label
    @text = text
    @cursor = text.scan(/./m).length
  end

  def insert(ch)
    chars = self.text.scan(/./m)
    chars.insert(@cursor, ch)
    @text = ""
    for ch in chars
      @text += ch
    end
    @cursor += 1
    @frame = 0
    self.changed = true
    self.invalidate
  end

  def delete
    chars = self.text.scan(/./m)
    chars.delete_at(@cursor - 1)
    @text = ""
    for ch in chars
      @text += ch
    end
    @cursor -= 1
    @frame = 0
    self.changed = true
    self.invalidate
  end

  def update
    @frame += 1
    @frame %= 20
    self.changed = false
    self.invalidate if ((@frame % 10) == 0)
    # Moving cursor
    if Input.triggerex?(:LEFT) || Input.repeatex?(:LEFT)
      if @cursor > 0
        @cursor -= 1
        @frame = 0
        self.invalidate
      end
      return
    end
    if Input.triggerex?(:LEFT) || Input.repeatex?(:RIGHT)
      if @cursor < self.text.scan(/./m).length
        @cursor += 1
        @frame = 0
        self.invalidate
      end
      return
    end
    # Backspace
    if Input.triggerex?(:BACKSPACE) || Input.repeatex?(:BACKSPACE) ||
       Input.triggerex?(:DELETE) || Input.repeatex?(:DELETE)
      self.delete if @cursor > 0
      return
    end
    # Letter & Number keys
    Input.gets.each_char { |c| insert(c) }
  end

  def refresh
    bitmap = self.bitmap
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    color = Color.new(120, 120, 120)
    bitmap.font.color = color
    bitmap.fill_rect(x, y, width, height, Color.new(0, 0, 0, 0))
    size = bitmap.text_size(self.label).width
    shadowtext(bitmap, x, y, size, height, self.label)
    x += size
    width -= size
    bitmap.fill_rect(x + 1, y + 1, width - 2, height - 2, color)
    ret = Rect.new(x + 1, y + 1, width - 2, height - 2)
    if !@captured
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(0, 0, 0, 0))
    else
      bitmap.fill_rect(x + 2, y + 2, width - 4, height - 4, Color.new(120, 120, 120, 80))
    end
    x += 4
    textscan = self.text.scan(/./m)
    scanlength = textscan.length
    @cursor = scanlength if @cursor > scanlength
    @cursor = 0 if @cursor < 0
    startpos = @cursor
    fromcursor = 0
    while (startpos > 0)
      c = textscan[startpos - 1]
      fromcursor += bitmap.text_size(c).width
      break if fromcursor > width - 4

      startpos -= 1
    end
    for i in startpos...scanlength
      c = textscan[i]
      textwidth = bitmap.text_size(c).width
      next if c == "\n"

      # Draw text
      shadowtext(bitmap, x, y, textwidth + 4, 32, c)
      # Draw cursor if necessary
      if ((@frame / 10) & 1) == 0 && i == @cursor
        bitmap.fill_rect(x, y + 4, 2, 24, Color.new(120, 120, 120))
      end
      # Add x to drawn text width
      x += textwidth
    end
    if ((@frame / 10) & 1) == 0 && textscan.length == @cursor
      bitmap.fill_rect(x, y + 4, 2, 24, Color.new(120, 120, 120))
    end
    return ret
  end
end

class Slider < UIControl
  attr_reader :minvalue
  attr_reader :maxvalue
  attr_accessor :curvalue
  attr_accessor :label

  def curvalue=(value)
    @curvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def minvalue=(value)
    @minvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def maxvalue=(value)
    @maxvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def setvalue
    if @setProc.nil?
      print "something went wrong ", caller
      return
    end
    @setProc.call(@curvalue)
  end

  def initialize(label, minvalue, maxvalue, curval, setblock = nil)
    super(label, setblock)
    @minvalue = minvalue
    @maxvalue = maxvalue
    @curvalue = curval
    @label = label
    @leftarrow = Rect.new(0, 0, 0, 0)
    @rightarrow = Rect.new(0, 0, 0, 0)
    self.minvalue = minvalue
    self.maxvalue = maxvalue
    self.curvalue = curval
  end

  def update
    mousepos = Mouse::getMousePos
    self.changed = false
    if self.minvalue < self.maxvalue && self.curvalue < self.minvalue
      self.curvalue = self.minvalue
    end
    return false if self.disabled
    return false if !Input.repeatex?(Input::LeftMouseKey) && !Input.triggerex?(Input::LeftMouseKey)
    return false if !mousepos

    left = toAbsoluteRect(@leftarrow)
    right = toAbsoluteRect(@rightarrow)
    oldvalue = self.curvalue
    # Left arrow
    if left.contains(mousepos[0], mousepos[1])
      if !$joiplay && Input.repeatcount(0x01) > 100
        self.curvalue -= 10
        self.curvalue = self.curvalue.floor
      elsif !$joiplay && Input.repeatcount(0x01) > 50
        self.curvalue -= 5
        self.curvalue = self.curvalue.floor
      else
        self.curvalue -= 1
        self.curvalue = self.curvalue.floor
      end
      self.changed = (self.curvalue != oldvalue)
      self.invalidate
    end
    # Right arrow
    if right.contains(mousepos[0], mousepos[1])
      if !$joiplay && Input.repeatcount(0x01) > 100
        self.curvalue += 10
        self.curvalue = self.curvalue.floor
      elsif !$joiplay && Input.repeatcount(0x01) > 50
        self.curvalue += 5
        self.curvalue = self.curvalue.floor
      else
        self.curvalue += 1
        self.curvalue = self.curvalue.floor
      end
      self.changed = (self.curvalue != oldvalue)
      self.invalidate
    end
  end

  def refresh
    bitmap = self.bitmap
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    color = Color.new(120, 120, 120)
    colordisabled = Color.new(120, 120, 120, 80)
    bitmap.fill_rect(x, y, width, height, Color.new(0, 0, 0, 0))
    size = bitmap.text_size(self.label).width
    leftarrows = bitmap.text_size(_INTL(" << "))
    numbers = bitmap.text_size(" XXXX ").width
    rightarrows = bitmap.text_size(_INTL(" >> "))
    bitmap.font.color = color
    shadowtext(bitmap, x, y, size, height, self.label)
    x += size
    shadowtext(
      bitmap, x, y, leftarrows.width, height, _INTL(" << "),
      self.disabled || self.curvalue == self.minvalue
    )
    @leftarrow = Rect.new(x, y, leftarrows.width, height)
    x += leftarrows.width
    if !self.disabled
      bitmap.font.color = color
      shadowtext(bitmap, x, y, numbers, height, " #{self.curvalue} ", false, 1)
    end
    x += numbers
    shadowtext(
      bitmap, x, y, rightarrows.width, height, _INTL(" >> "),
      self.disabled || self.curvalue == self.maxvalue
    )
    @rightarrow = Rect.new(x, y, rightarrows.width, height)
    x += rightarrows.width
  end
end

class OptionalSlider < Slider
  def initialize(label, minvalue, maxvalue, curvalue, setblock = nil)
    @slider = Slider.new(label, minvalue, maxvalue, curvalue)
    @checkbox = Checkbox.new("")
  end

  def curvalue
    return @checkbox.checked ? @slider.curvalue : nil
  end

  def curvalue=(value)
    slider.curvalue = value
  end

  def checked
    return @checkbox.checked
  end

  def checked=(value)
    @checkbox.checked = value
  end

  def setvalue
    if @setProc.nil?
      print "something went wrong ", caller
      return
    end
    @setProc.call(curvalue)
  end

  def invalid?
    return @slider.invalid? || @checkbox.invalid?
  end

  def invalidate
    @slider.invalidate
    @checkbox.invalidate
  end

  def validate?
    @slider.validate
    @checkbox.validate
  end

  def changed
    return @slider.changed || @checkbox.changed
  end

  def minvalue
    return @slider.minvalue
  end

  def minvalue=(value)
    slider.minvalue = value
  end

  def maxvalue
    return @slider.maxvalue
  end

  def maxvalue=(value)
    slider.maxvalue = value
  end

  def update
    updatedefs
    @slider.update
    @checkbox.update
  end

  def refresh
    updatedefs
    @slider.refresh
    @checkbox.refresh
  end

  private

  def updatedefs
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    checkboxwidth = 32
    @slider.bitmap = self.bitmap
    @slider.parent = self.parent
    @checkbox.x = self.x
    @checkbox.y = self.y
    @checkbox.width = checkboxwidth
    @checkbox.height = self.height
    @checkbox.bitmap = self.bitmap
    @checkbox.parent = self.parent
    @slider.x = self.x + checkboxwidth + 4
    @slider.y = self.y
    @slider.width = self.width - checkboxwidth
    @slider.height = self.height
    @slider.disabled = !@checkbox.checked
  end
end

class ArrayCountSlider < Slider
  def maxvalue
    return @array.length - 1
  end

  def initialize(array, label)
    @array = array
    super(label, 0, canvas.animation.length - 1, 0)
  end
end

class FrameCountSlider < Slider
  def maxvalue
    return @canvas.animation.length
  end

  def initialize(canvas)
    @canvas = canvas
    super(_INTL("Frame:"), 1, canvas.animation.length, 0)
  end

  def valueupdate(canvas, newvalue)
    @canvas = canvas
    super(_INTL("Frame:"), 1, canvas.animation.length, newvalue)
  end
end

class FrameCountButton < Button
  def label
    return _INTL("Total Frames: {1}", @canvas.animation.length)
  end

  def initialize(canvas)
    @canvas = canvas
    super(self.label)
  end
end

class TextSlider < UIControl
  attr_reader :minvalue
  attr_reader :maxvalue
  attr_reader :curvalue
  attr_accessor :label
  attr_accessor :options
  attr_accessor :maxoptionwidth

  def curvalue=(value)
    @curvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def minvalue=(value)
    @minvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def maxvalue=(value)
    @maxvalue = value
    @curvalue = self.minvalue if self.minvalue && @curvalue < self.minvalue
    @curvalue = self.maxvalue if self.maxvalue && @curvalue > self.maxvalue
    self.invalidate
  end

  def initialize(label, options, curval, setblock = nil)
    super(label, setblock)
    @label = label
    @options = options
    @minvalue = 0
    @maxvalue = options.length - 1
    @curvalue = curval
    @leftarrow = Rect.new(0, 0, 0, 0)
    @rightarrow = Rect.new(0, 0, 0, 0)
    self.minvalue = @minvalue
    self.maxvalue = @maxvalue
    self.curvalue = @curvalue
  end

  def setvalue
    if @setProc.nil?
      print "something went wrong ", caller
      return
    end
    @setProc.call(@curvalue)
  end

  def update
    mousepos = Mouse::getMousePos
    self.changed = false
    if self.minvalue < self.maxvalue && self.curvalue < self.minvalue
      self.curvalue = self.minvalue
    end
    return false if self.disabled
    return false if !Input.repeatex?(Input::LeftMouseKey) && !Input.triggerex?(Input::LeftMouseKey)
    return false if !mousepos

    left = toAbsoluteRect(@leftarrow)
    right = toAbsoluteRect(@rightarrow)
    oldvalue = self.curvalue
    # Left arrow
    if left.contains(mousepos[0], mousepos[1])
      if !$joiplay && Input.repeatcount(0x01) > 100
        self.curvalue -= 10
      elsif !$joiplay && Input.repeatcount(0x01) > 50
        self.curvalue -= 5
      else
        self.curvalue -= 1
      end
      self.changed = (self.curvalue != oldvalue)
      self.invalidate
    end
    # Right arrow
    if right.contains(mousepos[0], mousepos[1])
      if !$joiplay && Input.repeatcount(0x01) > 100
        self.curvalue += 10
      elsif !$joiplay && Input.repeatcount(0x01) > 50
        self.curvalue += 5
      else
        self.curvalue += 1
      end
      self.changed = (self.curvalue != oldvalue)
      self.invalidate
    end
  end

  def refresh
    bitmap = self.bitmap
    if @maxoptionwidth == nil
      for i in 0...@options.length
        w = self.bitmap.text_size(" " + @options[i] + " ").width
        @maxoptionwidth = w if !@maxoptionwidth || @maxoptionwidth < w
      end
    end
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    color = Color.new(120, 120, 120)
    colordisabled = Color.new(120, 120, 120, 80)
    bitmap.fill_rect(x, y, width, height, Color.new(0, 0, 0, 0))
    size = bitmap.text_size(self.label).width
    leftarrows = bitmap.text_size(_INTL(" << "))
    rightarrows = bitmap.text_size(_INTL(" >> "))
    bitmap.font.color = color
    shadowtext(bitmap, x, y, size, height, self.label)
    x += size
    shadowtext(
      bitmap, x, y, leftarrows.width, height, _INTL(" << "),
      self.disabled || self.curvalue == self.minvalue
    )
    @leftarrow = Rect.new(x, y, leftarrows.width, height)
    x += leftarrows.width
    if !self.disabled
      bitmap.font.color = color
      shadowtext(bitmap, x, y, @maxoptionwidth, height, " #{@options[self.curvalue]} ", false, 1)
    end
    x += @maxoptionwidth
    shadowtext(
      bitmap, x, y, rightarrows.width, height, _INTL(" >> "),
      self.disabled || self.curvalue == self.maxvalue
    )
    @rightarrow = Rect.new(x, y, rightarrows.width, height)
    x += rightarrows.width
  end
end

class OptionalTextSlider < TextSlider
  def initialize(label, options, curval, setblock = nil)
    @slider = TextSlider.new(label, options, curval)
    @checkbox = Checkbox.new("")
  end

  def curvalue
    return @checkbox.checked ? @slider.curvalue : nil
  end

  def curvalue=(value)
    slider.curvalue = value
  end

  def checked
    return @checkbox.checked
  end

  def checked=(value)
    @checkbox.checked = value
  end

  def invalid?
    return @slider.invalid? || @checkbox.invalid?
  end

  def invalidate
    @slider.invalidate
    @checkbox.invalidate
  end

  def validate?
    @slider.validate
    @checkbox.validate
  end

  def changed
    return @slider.changed || @checkbox.changed
  end

  def minvalue
    return @slider.minvalue
  end

  def minvalue=(value)
    slider.minvalue = value
  end

  def maxvalue
    return @slider.maxvalue
  end

  def maxvalue=(value)
    slider.maxvalue = value
  end

  def update
    updatedefs
    @slider.update
    @checkbox.update
  end

  def refresh
    updatedefs
    @slider.refresh
    @checkbox.refresh
  end

  private

  def updatedefs
    x = self.x
    y = self.y
    width = self.width
    height = self.height
    checkboxwidth = 32
    @slider.bitmap = self.bitmap
    @slider.parent = self.parent
    @checkbox.x = self.x
    @checkbox.y = self.y
    @checkbox.width = checkboxwidth
    @checkbox.height = self.height
    @checkbox.bitmap = self.bitmap
    @checkbox.parent = self.parent
    @slider.x = self.x + checkboxwidth + 4
    @slider.y = self.y
    @slider.width = self.width - checkboxwidth
    @slider.height = self.height
    @slider.disabled = !@checkbox.checked
  end
end

class ControlWindow < SpriteWindow_Base
  attr_reader :controls

  def initialize(x, y, width, height)
    super(x, y, width, height)
    self.contents = Bitmap.new(width - 32, height - 32)
    pbSetNarrowFont(self.contents)
    @controls = []
  end

  def dispose
    self.contents.dispose
    super
  end

  def refresh
    for i in 0...@controls.length
      @controls[i].refresh
    end
  end

  def repaint
    for i in 0...@controls.length
      @controls[i].repaint
    end
  end

  def invalidate
    for i in 0...@controls.length
      @controls[i].invalidate
    end
  end

  def hittest?(i)
    mousepos = Mouse::getMousePos
    return false if !mousepos
    return false if i < 0 || i >= @controls.length

    rc = Rect.new(
      @controls[i].parentX,
      @controls[i].parentY,
      @controls[i].width,
      @controls[i].height
    )
    return rc.contains(mousepos[0], mousepos[1])
  end

  def addControl(control, y: @controls.length)
    i = @controls.length
    @controls[i] = control
    @controls[i].x = 0
    @controls[i].y = y * 32
    @controls[i].width = self.contents.width
    @controls[i].height = 32
    @controls[i].parent = self
    @controls[i].bitmap = self.contents
    @controls[i].invalidate
    refresh
    return i
  end

  def freeAddControl(control, x: 0, y: 0)
    i = @controls.length
    y = y * 32
    @controls[i] = control
    @controls[i].x = x
    @controls[i].y = y
    @controls[i].width = self.contents.width
    @controls[i].height = 32
    @controls[i].parent = self
    @controls[i].bitmap = self.contents
    @controls[i].invalidate
    refresh
    return i
  end

  def addLabel(label, setproc = nil, y: @controls.length)
    return addControl(Label.new(label, setproc), y: y)
  end

  def addButton(label, setproc = nil, y: @controls.length)
    return addControl(Button.new(label, setproc), y: y)
  end

  def addSlider(label, minvalue, maxvalue, curvalue, setproc = nil, y: @controls.length)
    return addControl(Slider.new(label, minvalue, maxvalue, curvalue, setproc), y: y)
  end

  def addOptionalSlider(label, minvalue, maxvalue, curvalue, setproc = nil, y: @controls.length)
    return addControl(OptionalSlider.new(label, minvalue, maxvalue, curvalue, setproc), y: y)
  end

  def addTextSlider(label, options, curvalue, setproc = nil, y: @controls.length)
    return addControl(TextSlider.new(label, options, curvalue, setproc), y: y)
  end

  def addOptionalTextSlider(label, options, curvalue, setproc = nil, y: @controls.length)
    return addControl(OptionalTextSlider.new(label, options, curvalue, setproc), y: y)
  end

  def addCheckbox(label, setproc = nil, y: @controls.length)
    return addControl(Checkbox.new(label, setproc), y: y)
  end

  def addSpace(y = @controls.length)
    return addControl(UIControl.new(""), y: y)
  end

  def update
    super
    for i in 0...@controls.length
      @controls[i].update
    end
    repaint
  end

  def changed?(i)
    return false if i < 0

    return @controls[i].changed
  end

  def value(i)
    return false if i < 0

    return @controls[i].curvalue
  end
end

################################################################################
# Clipboard
################################################################################
module Clipboard
  @data = nil
  @typekey = ""

  def self.data
    return nil if !@data

    return Marshal::load(@data)
  end

  def self.typekey
    return @typekey
  end

  def self.setData(data, key)
    @data = Marshal.dump(data)
    @typekey = key
  end
end

################################################################################
# Collision testing
################################################################################
class Rect < Object
  def contains(x, y)
    return x >= self.x && x < self.x + self.width &&
           y >= self.y && y < self.y + self.height
  end
end

def pbSpriteHitTest(sprite, x, y, usealpha = true, wholecanvas = false)
  return false if !sprite || sprite.disposed?
  return false if !sprite.bitmap
  return false if !sprite.visible
  return false if sprite.bitmap.disposed?

  width = sprite.src_rect.width
  height = sprite.src_rect.height
  if !wholecanvas
    xwidth = width - 128
    xheight = height - 128
    width = 128 if width > 128 && !usealpha
    height = 128 if height > 128 && !usealpha
  else
    xwidth = 0
    xheight = 0
  end
  width = sprite.bitmap.width if width > sprite.bitmap.width
  height = sprite.bitmap.height if height > sprite.bitmap.height
  if usealpha
    spritex = sprite.x - (sprite.ox * sprite.zoom_x)
    spritey = sprite.y - (sprite.oy * sprite.zoom_y)
    width *= sprite.zoom_x
    height *= sprite.zoom_y
  else
    spritex = sprite.x - sprite.ox
    spritey = sprite.y - sprite.oy
    spritex += xwidth / 2
    spritey += xheight / 2
  end
  if !(x >= spritex && x <= spritex + width && y >= spritey && y <= spritey + height)
    return false
  end

  if usealpha
    # TODO: This should account for sprite.angle as well
    bitmapX = sprite.src_rect.x
    bitmapY = sprite.src_rect.y
    bitmapX += sprite.ox
    bitmapY += sprite.oy
    bitmapX += (x - sprite.x) / sprite.zoom_x if sprite.zoom_x > 0
    bitmapY += (y - sprite.y) / sprite.zoom_y if sprite.zoom_y > 0
    bitmapX = bitmapX.round
    bitmapY = bitmapY.round
    if sprite.mirror
      xmirror = bitmapX - sprite.src_rect.x
      bitmapX = sprite.src_rect.x + 192 - xmirror
    end
    color = sprite.bitmap.get_pixel(bitmapX, bitmapY)
    return false if (color.alpha == 0)
  end
  return true
end

def pbTrackPopupMenu(commands)
  mousepos = Mouse::getMousePos
  return -1 if !mousepos

  menuwindow = Window_Menu.new(commands, mousepos[0], mousepos[1])
  menuwindow.z = 99999
  loop do
    Graphics.update
    Input.update
    menuwindow.update
    hit = menuwindow.hittest
    menuwindow.index = hit if hit >= 0
    if Input.triggerex?(Input::LeftMouseKey) || Input.triggerex?(Input::RightMouseKey) # Left or right button
      menuwindow.dispose
      return hit
    end
    if Input.trigger?(Input::C)
      hit = menuwindow.index
      menuwindow.dispose
      return hit
    end
    if Input.trigger?(Input::B) # Escape
      break
    end
  end
  menuwindow.dispose
  return -1
end

################################################################################
# Sprite sheet scrolling bar
################################################################################
class AnimationWindow < SpriteWrapper
  attr_reader :animbitmap
  attr_reader :start
  attr_reader :selected

  NUMFRAMES = 5

  def initialize(x, y, width, height, viewport = nil)
    super(viewport)
    @animbitmap = nil
    @arrows = AnimatedBitmap.new("Graphics/Pictures/arrows")
    self.x = x
    self.y = y
    @start = 0
    @selected = 0
    @contents = Bitmap.new(width, height)
    self.bitmap = @contents
    refresh
  end

  def animbitmap=(val)
    @animbitmap = val
    @start = 0
    refresh
  end

  def selected=(val)
    @selected = val
    refresh
  end

  def dispose
    @contents.dispose
    @arrows.dispose
    @start = 0
    @selected = 0
    @changed = false
    super
  end

  def drawrect(bm, x, y, width, height, color)
    bm.fill_rect(x, y, width, 1, color)
    bm.fill_rect(x, y + height - 1, width, 1, color)
    bm.fill_rect(x, y, 1, height, color)
    bm.fill_rect(x + width - 1, y, 1, height, color)
  end

  def drawborder(bm, x, y, width, height, color)
    bm.fill_rect(x, y, width, 2, color)
    bm.fill_rect(x, y + height - 2, width, 2, color)
    bm.fill_rect(x, y, 2, height, color)
    bm.fill_rect(x + width - 2, y, 2, height, color)
  end

  def refresh
    arrowwidth = @arrows.bitmap.width / 2
    @contents.clear
    @contents.fill_rect(0, 0, @contents.width, @contents.height, Color.new(180, 180, 180))
    @contents.blt(0, 0, @arrows.bitmap, Rect.new(0, 0, arrowwidth, 96))
    @contents.blt(arrowwidth + NUMFRAMES * 96, 0, @arrows.bitmap, Rect.new(arrowwidth, 0, arrowwidth, 96))
    havebitmap = (self.animbitmap && !self.animbitmap.disposed?)
    if havebitmap
      rect = Rect.new(0, 0, 0, 0)
      rectdst = Rect.new(0, 0, 0, 0)
      x = arrowwidth
      for i in 0...NUMFRAMES
        j = i + @start
        rect.set((j % 5) * 192, (j / 5) * 192, 192, 192)
        rectdst.set(x, 0, 96, 96)
        @contents.stretch_blt(rectdst, self.animbitmap, rect)
        x += 96
      end
    end
    for i in 0...NUMFRAMES
      drawrect(@contents, arrowwidth + i * 96, 0, 96, 96, Color.new(100, 100, 100))
      if @start + i == @selected && havebitmap
        drawborder(@contents, arrowwidth + i * 96, 0, 96, 96, Color.new(255, 0, 0))
      end
    end
  end

  def changed?
    return @changed
  end

  def update
    mousepos = Mouse::getMousePos
    @changed = false
    return if !Input.repeatex?(Input::LeftMouseKey)
    return if !mousepos
    return if !self.animbitmap

    arrowwidth = @arrows.bitmap.width / 2
    maxindex = (self.animbitmap.height / 192) * 5
    left = Rect.new(0, 0, arrowwidth, 96)
    right = Rect.new(arrowwidth + NUMFRAMES * 96, 0, arrowwidth, 96)
    left.x += self.x
    left.y += self.y
    right.x += self.x
    right.y += self.y
    swatchrects = []
    for i in 0...NUMFRAMES
      swatchrects.push(Rect.new(arrowwidth + i * 96 + self.x, self.y, 96, 96))
    end
    for i in 0...NUMFRAMES
      if swatchrects[i].contains(mousepos[0], mousepos[1])
        @selected = @start + i
        @changed = true
        refresh
        return
      end
    end
    # Left arrow
    if left.contains(mousepos[0], mousepos[1])
      if Input.repeatcount(0x01) > 30
        @start -= 3
      else
        @start -= 1
      end
      @start = 0 if @start < 0
      refresh
    end
    # Right arrow
    if right.contains(mousepos[0], mousepos[1])
      if Input.repeatcount(0x01) > 30
        @start += 3
      else
        @start += 1
      end
      @start = maxindex if @start >= maxindex
      refresh
    end
  end
end

class CanvasAnimationWindow < AnimationWindow
  def animbitmap
    return @canvas.animbitmap
  end

  def initialize(canvas, x, y, width, height, viewport = nil)
    @canvas = canvas
    super(x, y, width, height, viewport)
  end
end

################################################################################
# Cel sprite
################################################################################
class InvalidatableSprite < Sprite
  def initialize(viewport = nil)
    super(viewport)
    @invalid = false
  end

  # Marks that the control must be redrawn to reflect current logic.
  def invalidate
    @invalid = true
  end

  # Determines whether the control is invalid
  def invalid?
    return @invalid
  end

  # Marks that the control is valid.  Normally called only by repaint.
  def validate
    @invalid = false
  end

  # Redraws the sprite only if it is invalid, and then revalidates the sprite
  def repaint
    if self.invalid?
      refresh
      validate
    end
  end

  # Redraws the sprite.  This method should not check whether
  # the sprite is invalid, to allow it to be explicitly called.
  def refresh
  end

  # Updates the logic on the sprite, for example, in response
  # to user input, and invalidates it if necessary.  This should
  # be called only once per frame.
  def update
    super
  end
end

class SpriteFrame < InvalidatableSprite
  attr_reader :id
  attr_reader :locked
  attr_reader :selected
  attr_reader :sprite

  def sprite=(value)
    @sprite = value
    self.invalidate
  end

  def locked=(value)
    @locked = value
    self.invalidate
  end

  def selected=(value)
    @selected = value
    self.invalidate
  end

  def initialize(id, sprite, viewport, previous = false)
    super(viewport)
    @id = id
    @sprite = sprite
    @previous = previous
    @locked = false
    @selected = false
    @contents = Bitmap.new(128, 128)
    @iconbitmap = AnimatedBitmap.new("Graphics/Pictures/animFrameIcon")
    self.bitmap = @contents
    self.invalidate
  end

  def dispose
    @contents.dispose
    super
  end

  def refresh
    selcolor = Color.new(0, 0, 0)
    unselcolor = Color.new(220, 220, 220)
    prevcolor = Color.new(64, 128, 192)
    @contents.clear
    color = (@previous) ? prevcolor : (@selected) ? selcolor : unselcolor
    @contents.fill_rect(0, 0, 128, 1, color)
    @contents.fill_rect(0, 127, 128, 1, color)
    @contents.fill_rect(0, 0, 1, 128, color)
    @contents.fill_rect(127, 0, 1, 128, color)
    yoffset = (@previous) ? 64 : 0
    bmrect = Rect.new((@id % 10) * 16, yoffset + (@id / 10) * 16, 16, 16)
    @contents.blt(0, 0, @iconbitmap.bitmap, bmrect)
    if @locked && !@previous
      bmrect = Rect.new(0, 48, 16, 16)
      @contents.blt(16, 0, @iconbitmap.bitmap, bmrect)
    end
  end
end

################################################################################
# Mini battle scene
################################################################################
class MiniBattler
  attr_accessor :index
  attr_accessor :pokemon

  def initialize(index); self.index = index; end
end

class MiniBattle
  attr_accessor :battlers

  def initialize
    @battlers = []
    for i in 0...4; @battlers[i] = MiniBattler.new(i); end
  end
end

################################################################################
# Canvas
################################################################################
class AnimationCanvas < Sprite
  attr_reader :viewport
  attr_reader :sprites
  attr_reader :currentframe # Currently active frame
  attr_reader :currentcel
  attr_reader :animation # Currently selected animation
  attr_reader :animbitmap # Currently selected animation bitmap
  attr_accessor :pattern  # Currently selected pattern
  attr_accessor :createLock

  BORDERSIZE = 64

  def initialize(animation, viewport = nil)
    super(viewport)
    @createLock = false
    @currentframe = 0
    @currentcel = -1
    @pattern = 0
    @sprites = {}
    @celsprites = []
    @framesprites = []
    @lastframesprites = []
    @dirty = []
    @viewport = viewport
    @selecting = false
    @selectOffsetX = 0
    @selectOffsetY = 0
    @playing = false
    @playingframe = 0
    @player = nil
    @battle = MiniBattle.new
    @user = AnimatedBitmap.new("Graphics/Pictures/testback").deanimate
    @target = AnimatedBitmap.new("Graphics/Pictures/testfront").deanimate
    @testscreen = AnimatedBitmap.new("Graphics/Pictures/testscreen")
    self.bitmap = @testscreen.bitmap
    for i in 0...PBAnimation::MAXSPRITES
      @lastframesprites[i] = SpriteFrame.new(i, @celsprites[i], viewport, true)
      @lastframesprites[i].selected = false
      @lastframesprites[i].visible = false
    end
    for i in 0...PBAnimation::MAXSPRITES
      @celsprites[i] = Sprite.new(viewport)
      @celsprites[i].visible = false
      @celsprites[i].src_rect = Rect.new(0, 0, 0, 0)
      @celsprites[i].bitmap = nil
      @framesprites[i] = SpriteFrame.new(i, @celsprites[i], viewport)
      @framesprites[i].selected = false
      @framesprites[i].visible = false
      @dirty[i] = true
    end
    loadAnimation(animation)
  end

  def loadAnimation(anim)
    @animation = anim
    @animbitmap.dispose if @animbitmap
    if @animation.graphic == ""
      @animbitmap = nil
    else
      begin
        @animbitmap = AnimatedBitmap.new(
          "Graphics/Animations/" + @animation.graphic,
          @animation.hue
        ).deanimate
      rescue
        @animbitmap = nil
      end
    end
    @currentcel = -1
    self.currentframe = 0
    @selecting = false
    @pattern = 0
    self.invalidate
  end

  def animbitmap=(value)
    @animbitmap.dispose if @animbitmap
    @animbitmap = value
    for i in 2...PBAnimation::MAXSPRITES
      @celsprites[i].bitmap = @animbitmap if @celsprites[i]
    end
    self.invalidate
  end

  def dispose
    @user.dispose
    @target.dispose
    @animbitmap.dispose if @animbitmap
    @selectedbitmap.dispose if @selectedbitmap
    @celbitmap.dispose if @celbitmap
    self.bitmap.dispose if self.bitmap
    for i in 0...PBAnimation::MAXSPRITES
      @celsprites[i].dispose if @celsprites[i]
    end
    super
  end

  def play(oppmove = false)
    if !@playing
      @sprites["pokemon0"] = Sprite.new(@viewport)
      @sprites["pokemon0"].bitmap = @user
      @sprites["pokemon0"].z = 21
      @sprites["pokemon1"] = Sprite.new(@viewport)
      @sprites["pokemon1"].bitmap = @target
      @sprites["pokemon1"].z = 16
      pbSpriteSetAnimFrame(
        @sprites["pokemon0"],
        pbCreateCel(
          PBScene::FOCUSUSER_X,
          PBScene::FOCUSUSER_Y, -1, 2
        ),
        @sprites["pokemon0"], @sprites["pokemon1"]
      )
      pbSpriteSetAnimFrame(
        @sprites["pokemon1"],
        pbCreateCel(
          PBScene::FOCUSTARGET_X,
          PBScene::FOCUSTARGET_Y, -2, 1
        ),
        @sprites["pokemon0"], @sprites["pokemon1"]
      )
      usersprite = @sprites["pokemon#{oppmove ? 1 : 0}"]
      targetsprite = @sprites["pokemon#{oppmove ? 0 : 1}"]
      olduserx = usersprite ? usersprite.x : 0
      oldusery = usersprite ? usersprite.y : 0
      oldtargetx = targetsprite ? targetsprite.x : 0
      oldtargety = targetsprite ? targetsprite.y : 0
      @player = PBAnimationPlayerX.new(
        @animation,
        @battle.battlers[oppmove ? 1 : 0], @battle.battlers[oppmove ? 0 : 1], self, nil, oppmove, true
      )
      userwidth = (!usersprite || !usersprite.bitmap || usersprite.bitmap.disposed?) ? 128 : usersprite.bitmap.width
      userheight = (!usersprite || !usersprite.bitmap || usersprite.bitmap.disposed?) ? 128 : usersprite.bitmap.height
      targetwidth = (!targetsprite.bitmap || targetsprite.bitmap.disposed?) ? 128 : targetsprite.bitmap.width
      targetheight = (!targetsprite.bitmap || targetsprite.bitmap.disposed?) ? 128 : targetsprite.bitmap.height
      @player.setLineTransform(
        PBScene::FOCUSUSER_X, PBScene::FOCUSUSER_Y,
        PBScene::FOCUSTARGET_X, PBScene::FOCUSTARGET_Y,
        olduserx, oldusery,
        oldtargetx, oldtargety
      )
      @player.start
      @playing = true
      @sprites["pokemon0"].x += BORDERSIZE
      @sprites["pokemon0"].y += BORDERSIZE
      @sprites["pokemon1"].x += BORDERSIZE
      @sprites["pokemon1"].y += BORDERSIZE
      prevoldstate = []
      oldstate = []
      for i in 0...PBAnimation::MAXSPRITES
        oldstate.push([@celsprites[i].visible, @framesprites[i].visible, @lastframesprites[i].visible])
        @celsprites[i].visible = false
        @framesprites[i].visible = false
        @lastframesprites[i].visible = false
      end
      begin
        Graphics.update
        self.update
      end while @playing
      for i in 0...PBAnimation::MAXSPRITES
        @celsprites[i].visible = oldstate[i][0]
        @framesprites[i].visible = oldstate[i][1]
        @lastframesprites[i].visible = oldstate[i][2]
      end
      @sprites["pokemon0"].dispose
      @sprites["pokemon1"].dispose
      @player.dispose
      @player = nil
    end
  end

  def invalidate
    for i in 0...PBAnimation::MAXSPRITES
      invalidateCel(i)
    end
  end

  def invalidateCel(i)
    @dirty[i] = true
  end

  def currentframe=(value)
    @currentframe = value
    invalidate
  end

  def getCurrentFrame
    return nil if @currentframe >= @animation.length

    return @animation[@currentframe]
  end

  def setFrame(i)
    if @celsprites[i]
      @framesprites[i].ox = 64
      @framesprites[i].oy = 64
      @framesprites[i].selected = (i == @currentcel)
      @framesprites[i].locked = self.locked?(i)
      @framesprites[i].x = @celsprites[i].x
      @framesprites[i].y = @celsprites[i].y
      @framesprites[i].visible = @celsprites[i].visible
      @framesprites[i].repaint
    end
  end

  def setPreviousFrame(i)
    if @currentframe > 0
      cel = @animation[@currentframe - 1][i]
      if cel != nil
        @lastframesprites[i].ox = 64
        @lastframesprites[i].oy = 64
        @lastframesprites[i].selected = false
        @lastframesprites[i].locked = false
        @lastframesprites[i].x = cel[AnimFrame::X] + 64
        @lastframesprites[i].y = cel[AnimFrame::Y] + 64
        @lastframesprites[i].visible = true
        @lastframesprites[i].repaint
      else
        @lastframesprites[i].visible = false
      end
    else
      @lastframesprites[i].visible = false
    end
  end

  def offsetFrame(frame, ox, oy)
    if frame >= 0 && frame < @animation.length
      for i in 0...PBAnimation::MAXSPRITES
        if !self.locked?(i) && @animation[frame][i]
          @animation[frame][i][AnimFrame::X] += ox
          @animation[frame][i][AnimFrame::Y] += oy
        end
        @dirty[i] = true if frame == @currentframe
      end
    end
  end

  # Clears all items in the frame except locked items
  def clearFrame(frame)
    if frame >= 0 && frame < @animation.length
      for i in 0...PBAnimation::MAXSPRITES
        if self.deletable?(i)
          @animation[frame][i] = nil
        else
          pbResetCel(@animation[frame][i])
        end
        @dirty[i] = true if frame == @currentframe
      end
    end
  end

  def insertFrame(frame)
    return if frame >= @animation.length

    @animation.insert(frame, @animation[frame].clone)
    self.invalidate
  end

  def copyFrame(src, dst, reversecopy = false)
    return if dst >= @animation.length

    for i in 0...PBAnimation::MAXSPRITES
      if reversecopy
        cloned2 = @animation[dst][i]
      end
      clonedframe = @animation[src][i]
      clonedframe = clonedframe.clone if clonedframe && clonedframe != true
      @animation[dst][i] = clonedframe
      if reversecopy
        cloned2 = cloned2.clone if cloned2 && cloned2 != true
        @animation[src][i] = cloned2
      end
    end
    self.invalidate if dst == @currentframe
  end

  def pasteFrame(frame)
    return if frame < 0 || frame >= @animation.length
    return if Clipboard.typekey != "PBAnimFrame"

    @animation[frame] = Clipboard.data
    self.invalidate if frame == @currentframe
  end

  def deleteFrame(frame)
    return if frame < 0 || frame >= @animation.length || @animation.length <= 1

    self.currentframe -= 1 if frame == @animation.length - 1
    @animation.delete_at(frame)
    @currentcel = -1
    self.invalidate
  end

  # This frame becomes a copy of the previous frame
  def pasteLast
    copyFrame(@currentframe - 1, @currentframe) if @currentframe > 0
  end

  def currentCel
    return nil if @currentcel < 0
    return nil if @currentframe >= @animation.length

    return @animation[@currentframe][@currentcel]
  end

  def pasteCel(x, y)
    return if @currentframe >= @animation.length
    return if Clipboard.typekey != "PBAnimCel"

    for i in 0...PBAnimation::MAXSPRITES
      next if @animation[@currentframe][i]

      @animation[@currentframe][i] = Clipboard.data
      cel = @animation[@currentframe][i]
      cel[AnimFrame::X] = x
      cel[AnimFrame::Y] = y
      cel[AnimFrame::LOCKED] = 0
      @celsprites[i].bitmap = @user if cel[AnimFrame::PATTERN] == -1
      @celsprites[i].bitmap = @target if cel[AnimFrame::PATTERN] == -2
      @currentcel = i
      break
    end
    invalidate
  end

  def deleteCel(cel)
    return if cel < 0
    return if @currentframe < 0 || @currentframe >= @animation.length
    return if !deletable?(cel)

    @animation[@currentframe][cel] = nil
    @dirty[cel] = true
  end

  def swapCels(cel1, cel2, frame = @currentframe)
    return if cel1 < 0 || cel2 < 0
    return if frame < 0 || frame >= @animation.length

    t = @animation[frame][cel1]
    @animation[frame][cel1] = @animation[frame][cel2]
    @animation[frame][cel2] = t
    @currentcel = cel2
    @dirty[cel1] = true
    @dirty[cel2] = true
  end

  def listSelect(canvas, mousex, mousey)
    listSelectable = []
    celselected = false
    for i in 0...PBAnimation::MAXSPRITES
      if pbSpriteHitTest(@celsprites[i], mousex, mousey, false, true)
        listSelectable.push(i)
      end
    end
    if listSelectable.length == 0
      Kernel.pbMessage(_INTL("No cels at the clicked position to select."))
      return
    end
    winy = (listSelectable.length * 32) + 64 # for no. of cels, plus buffer area and cancel button
    listwin = ControlWindow.new(0, 0, 320, winy)
    listwin.z = 99999
    for j in 0...listSelectable.length
      listwin.addButton(_INTL("Cel {1}", listSelectable[j].to_s))
    end
    cancelbutton = listwin.addButton(_INTL("Cancel"))
    loop do
      Graphics.update
      Input.update
      listwin.update
      if listwin.changed?(cancelbutton) || Input.trigger?(Input::B)
        break
      end

      for k in 0...listSelectable.length
        if listwin.changed?(k)
          @currentcel = listSelectable[k]
          invalidate
          celselected = true
        end
        break if celselected == true
      end
      break if celselected == true
    end
    listwin.dispose
  end

  def locked?(celindex)
    cel = @animation[self.currentframe]
    return false if !cel

    cel = cel[celindex]
    return cel ? (cel[AnimFrame::LOCKED] != 0) : false
  end

  def deletable?(celindex)
    cel = @animation[self.currentframe]
    return true if !cel

    cel = cel[celindex]
    return true if !cel
    return false if cel[AnimFrame::LOCKED] != 0

    if cel[AnimFrame::PATTERN] < 0
      count = 0
      pattern = cel[AnimFrame::PATTERN]
      for i in 0...PBAnimation::MAXSPRITES
        othercel = @animation[self.currentframe][i]
        count += 1 if othercel && othercel[AnimFrame::PATTERN] == pattern
      end
      return false if count <= 1
    end
    return true
  end

  def setBitmap(i, frame)
    if @celsprites[i]
      cel = @animation[frame][i]
      @celsprites[i].bitmap = @animbitmap
      if cel
        @celsprites[i].bitmap = @user if cel[AnimFrame::PATTERN] == -1
        @celsprites[i].bitmap = @target if cel[AnimFrame::PATTERN] == -2
      end
    end
  end

  def setSpriteBitmap(sprite, cel)
    if sprite && !sprite.disposed?
      sprite.bitmap = @animbitmap
      if cel
        sprite.bitmap = @user if cel[AnimFrame::PATTERN] == -1
        sprite.bitmap = @target if cel[AnimFrame::PATTERN] == -2
      end
    end
  end

  def addSprite(x, y)
    return false if @currentframe >= @animation.length || @createLock == true

    for i in 0...PBAnimation::MAXSPRITES
      next if @animation[@currentframe][i]

      @animation[@currentframe][i] = pbCreateCel(x, y, @pattern, @animation.position)
      @dirty[i] = true
      @currentcel = i
      return true
    end
    return false
  end

  def updateInput
    cel = currentCel
    mousepos = Mouse::getMousePos
    if mousepos && pbSpriteHitTest(self, mousepos[0], mousepos[1], false, true)
      if Input.triggerex?(Input::LeftMouseKey) # Left mouse button
        selectedcel = -1
        usealpha = (Input.triggerex?(:LALT)) ? true : false
        for j in 0...PBAnimation::MAXSPRITES
          if pbSpriteHitTest(@celsprites[j], mousepos[0], mousepos[1], usealpha, false)
            selectedcel = j
          end
        end
        if selectedcel < 0
          if @animbitmap && addSprite(mousepos[0] - BORDERSIZE, mousepos[1] - BORDERSIZE)
            @selecting = true if !self.locked?(@currentcel)
            @selectOffsetX = 0
            @selectOffsetY = 0
            cel = currentCel
            invalidate
          end
        else
          @currentcel = selectedcel
          @selecting = true if !self.locked?(@currentcel)
          cel = currentCel
          @selectOffsetX = cel[AnimFrame::X] - mousepos[0] + BORDERSIZE
          @selectOffsetY = cel[AnimFrame::Y] - mousepos[1] + BORDERSIZE
          invalidate
        end
      end
    end
    currentFrame = getCurrentFrame
    if currentFrame && !@selecting && (Input.triggerex?(:TAB) || Input.repeatex?(:TAB))
      goback = Input.press?(Input::X)
      currentFrame.length.times {
        if goback == false
          @currentcel += 1
          @currentcel = 0 if @currentcel >= currentFrame.length
        else
          @currentcel -= 1
          @currentcel = currentFrame.length - 1 if @currentcel < 0
        end
        break if currentFrame[@currentcel]
      }
      invalidate
      return
    end
    if cel && @selecting && mousepos
      cel[AnimFrame::X] = mousepos[0] - BORDERSIZE + @selectOffsetX
      cel[AnimFrame::Y] = mousepos[1] - BORDERSIZE + @selectOffsetY
      @dirty[@currentcel] = true
    end
    if !Input.getstate(0x01) && @selecting
      @selecting = false
    end
    if cel
      if (Input.triggerex?(:DELETE) || Input.repeatex?(:DELETE)) && self.deletable?(@currentcel)
        @animation[@currentframe][@currentcel] = nil
        @dirty[@currentcel] = true
        return
      end
      if Input.triggerex?(:P) || Input.repeatex?(:P)   # Properties
        pbCellProperties(self)
        @dirty[@currentcel] = true
        return
      end
      if Input.triggerex?(:L) || Input.repeatex?(:L)   # Lock
        cel[AnimFrame::LOCKED] = (cel[AnimFrame::LOCKED] == 0) ? 1 : 0
        @dirty[@currentcel] = true
      end
      if Input.triggerex?(:R) || Input.repeatex?(:R)   # Rotate right
        cel[AnimFrame::ANGLE] += 10
        cel[AnimFrame::ANGLE] %= 360
        @dirty[@currentcel] = true
      end
      if Input.triggerex?(:E) || Input.repeatex?(:E)   # Rotate left
        cel[AnimFrame::ANGLE] -= 10
        cel[AnimFrame::ANGLE] %= 360
        @dirty[@currentcel] = true
      end
      if Input.triggerex?(0x51) || Input.repeatex?(0x51)   # Zoom in
        cel[AnimFrame::ZOOMX] += 10
        cel[AnimFrame::ZOOMX] = 1000 if cel[AnimFrame::ZOOMX] > 1000
        cel[AnimFrame::ZOOMY] += 10
        cel[AnimFrame::ZOOMY] = 1000 if cel[AnimFrame::ZOOMY] > 1000
        @dirty[@currentcel] = true
      end
      if Input.triggerex?(0x57) || Input.repeatex?(0x57)   # Zoom out
        cel[AnimFrame::ZOOMX] -= 10
        cel[AnimFrame::ZOOMX] = 10 if cel[AnimFrame::ZOOMX] < 10
        cel[AnimFrame::ZOOMY] -= 10
        cel[AnimFrame::ZOOMY] = 10 if cel[AnimFrame::ZOOMY] < 10
        @dirty[@currentcel] = true
      end
      if Input.triggerex?(0x4E) || Input.repeatex?(0x4E)   # Renumber
        if currentcel && currentcel >= 2
          cel1 = currentcel
          cel2 = pbChooseNum(cel1)
          if cel2 >= 2 && cel1 != cel2
            swapCels(cel1, cel2)
          end
        end
      end
      if !self.locked?(@currentcel)
        if Input.repeat?(Input::UP)
          increment = (Input.press?(Input::A)) ? 2 : 8
          snap = (Input.press?(Input::X)) ? true : false
          cel[AnimFrame::Y] -= (cel[AnimFrame::Y] % increment) if snap
          cel[AnimFrame::Y] -= increment
          @dirty[@currentcel] = true
        end
        if Input.repeat?(Input::DOWN)
          increment = (Input.press?(Input::A)) ? 2 : 8
          snap = (Input.press?(Input::X)) ? true : false
          cel[AnimFrame::Y] -= (cel[AnimFrame::Y] % increment) if snap
          cel[AnimFrame::Y] += increment
          @dirty[@currentcel] = true
        end
        if Input.repeat?(Input::LEFT)
          increment = (Input.press?(Input::A)) ? 2 : 8
          snap = (Input.press?(Input::X)) ? true : false
          cel[AnimFrame::X] -= (cel[AnimFrame::X] % increment) if snap
          cel[AnimFrame::X] -= increment
          @dirty[@currentcel] = true
        end
        if Input.repeat?(Input::RIGHT)
          increment = (Input.press?(Input::A)) ? 2 : 8
          snap = (Input.press?(Input::X)) ? true : false
          cel[AnimFrame::X] -= (cel[AnimFrame::X] % increment) if snap
          cel[AnimFrame::X] += increment
          @dirty[@currentcel] = true
        end
      end
    end
  end

  def update
    super
    if @playing
      if !@player.playing?
        @playing = false
        invalidate
      else
        @player.update
      end
      return
    end
    updateInput
    #    @testscreen.update
    #    self.bitmap=@testscreen.bitmap
    if @currentframe < @animation.length
      for i in 0...PBAnimation::MAXSPRITES
        if @dirty[i]
          if @celsprites[i]
            setBitmap(i, @currentframe)
            pbSpriteSetAnimFrame(@celsprites[i], @animation[@currentframe][i], @celsprites[0], @celsprites[1], true)
            @celsprites[i].x += BORDERSIZE
            @celsprites[i].y += BORDERSIZE
          end
          setPreviousFrame(i)
          setFrame(i)
          @dirty[i] = false
        end
      end
    else
      for i in 0...PBAnimation::MAXSPRITES
        pbSpriteSetAnimFrame(@celsprites[i], nil, @celsprites[0], @celsprites[1], true)
        @celsprites[i].x += BORDERSIZE
        @celsprites[i].y += BORDERSIZE
        setPreviousFrame(i)
        setFrame(i)
        @dirty[i] = false
      end
    end
  end
end

################################################################################
# Pop-up menus for individual cels (and a couple for the batch editor)
################################################################################
def pbChooseNum(cel)
  ret = cel
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 5)
  sliderwin2.z = 99999
  sliderwin2.addLabel(_INTL("Old Number: {1}", cel))
  sliderwin2.addSlider(_INTL("New Number:"), 2, PBAnimation::MAXSPRITES, cel)
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      ret = sliderwin2.value(1)
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      ret = -1
      break
    end
  end
  sliderwin2.dispose
  return ret
end

def pbSetTone(cel, previewsprite)
  sliderwin2 = ControlWindow.new(0, 0, 320, 320)
  sliderwin2.z = 99999
  sliderwin2.addSlider(_INTL("Red Offset:"), -255, 255, cel[AnimFrame::TONERED])
  sliderwin2.addSlider(_INTL("Green Offset:"), -255, 255, cel[AnimFrame::TONEGREEN])
  sliderwin2.addSlider(_INTL("Blue Offset:"), -255, 255, cel[AnimFrame::TONEBLUE])
  sliderwin2.addSlider(_INTL("Gray Tone:"), 0, 255, cel[AnimFrame::TONEGRAY])
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    previewsprite.tone.set(
      sliderwin2.value(0),
      sliderwin2.value(1),
      sliderwin2.value(2),
      sliderwin2.value(3)
    )
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      cel[AnimFrame::TONERED] = sliderwin2.value(0)
      cel[AnimFrame::TONEGREEN] = sliderwin2.value(1)
      cel[AnimFrame::TONEBLUE] = sliderwin2.value(2)
      cel[AnimFrame::TONEGRAY] = sliderwin2.value(3)
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbSetToneBatch(tonehash)
  sliderwin2 = ControlWindow.new(0, 0, 320, 320)
  sliderwin2.z = 99999
  sliderwin2.addLabel(_INTL("Note: Pressing cancel will"))
  sliderwin2.addLabel(_INTL("clear previously OK'd changes"))
  sliderwin2.addSpace
  sliderwin2.addOptionalSlider(_INTL("Red Offset:"), -255, 255, tonehash["colortonered"])
  sliderwin2.addOptionalSlider(_INTL("Green Offset:"), -255, 255, tonehash["colortonegreen"])
  sliderwin2.addOptionalSlider(_INTL("Blue Offset:"), -255, 255, tonehash["colortoneblue"])
  sliderwin2.addOptionalSlider(_INTL("Gray Tone:"), 0, 255, tonehash["colortonegray"])
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      if sliderwin2.value(3)
        tonehash["toneredchange"] = true
        tonehash["colortonered"] = sliderwin2.value(3)
      else
        tonehash["toneredchange"] = false
      end
      if sliderwin2.value(4)
        tonehash["tonegreenchange"] = true
        tonehash["colortonegreen"] = sliderwin2.value(4)
      else
        tonehash["tonegreenchange"] = false
      end
      if sliderwin2.value(5)
        tonehash["tonebluechange"] = true
        tonehash["colortoneblue"] = sliderwin2.value(5)
      else
        tonehash["tonebluechange"] = false
      end
      if sliderwin2.value(6)
        tonehash["tonegraychange"] = true
        tonehash["colortonegray"] = sliderwin2.value(6)
      else
        tonehash["tonegraychange"] = false
      end
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      tonehash["toneredchange"] = false
      tonehash["tonegreenchange"] = false
      tonehash["tonebluechange"] = false
      tonehash["tonegraychange"] = false
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbSetFlash(cel, previewsprite)
  sliderwin2 = ControlWindow.new(0, 0, 320, 320)
  sliderwin2.z = 99999
  sliderwin2.addSlider(_INTL("Red:"), 0, 255, cel[AnimFrame::COLORRED])
  sliderwin2.addSlider(_INTL("Green:"), 0, 255, cel[AnimFrame::COLORGREEN])
  sliderwin2.addSlider(_INTL("Blue:"), 0, 255, cel[AnimFrame::COLORBLUE])
  sliderwin2.addSlider(_INTL("Alpha:"), 0, 255, cel[AnimFrame::COLORALPHA])
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    previewsprite.tone.set(
      sliderwin2.value(0),
      sliderwin2.value(1),
      sliderwin2.value(2),
      sliderwin2.value(3)
    )
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      cel[AnimFrame::COLORRED] = sliderwin2.value(0)
      cel[AnimFrame::COLORGREEN] = sliderwin2.value(1)
      cel[AnimFrame::COLORBLUE] = sliderwin2.value(2)
      cel[AnimFrame::COLORALPHA] = sliderwin2.value(3)
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbSetFlashBatch(blendhash)
  sliderwin2 = ControlWindow.new(0, 0, 320, 320)
  sliderwin2.z = 99999
  sliderwin2.addLabel(_INTL("Note: Pressing cancel will"))
  sliderwin2.addLabel(_INTL("clear previously OK'd changes"))
  sliderwin2.addSpace
  sliderwin2.addOptionalSlider(_INTL("Red:"), 0, 255, blendhash["blendcolorred"])
  sliderwin2.addOptionalSlider(_INTL("Green:"), 0, 255, blendhash["blendcolorgreen"])
  sliderwin2.addOptionalSlider(_INTL("Blue:"), 0, 255, blendhash["blendcolorblue"])
  sliderwin2.addOptionalSlider(_INTL("Alpha:"), 0, 255, blendhash["blendcoloralpha"])
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      if sliderwin2.value(3)
        blendhash["blendredchange"] = true
        blendhash["blendcolorred"] = sliderwin2.value(3)
      else
        blendhash["blendredchange"] = false
      end
      if sliderwin2.value(4)
        blendhash["blendgreenchange"] = true
        blendhash["blendcolorgreen"] = sliderwin2.value(4)
      else
        blendhash["blendgreenchange"] = false
      end
      if sliderwin2.value(5)
        blendhash["blendbluechange"] = true
        blendhash["blendcolorblue"] = sliderwin2.value(5)
      else
        blendhash["blendbluechange"] = false
      end
      if sliderwin2.value(6)
        blendhash["blendalphachange"] = true
        blendhash["blendcoloralpha"] = sliderwin2.value(6)
      else
        blendhash["blendalphachange"] = false
      end
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      blendhash["blendredchange"] = false
      blendhash["blendgreenchange"] = false
      blendhash["blendbluechange"] = false
      blendhash["blendalphachange"] = false
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbCellProperties(canvas)
  cel = canvas.currentCel.clone # Clone cell, in case operation is canceled
  return if !cel

  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 16)
  previewwin = ControlWindow.new(320, 0, 192, 192)
  sliderwin2.viewport = canvas.viewport
  previewwin.viewport = canvas.viewport
  previewsprite = Sprite.new(canvas.viewport)
  previewsprite.bitmap = canvas.animbitmap
  previewsprite.z = previewwin.z + 1
  sliderwin2.z = previewwin.z + 2
  set0 = sliderwin2.addSlider(_INTL("Pattern:"), -2, 1000, cel[AnimFrame::PATTERN])
  set1 = sliderwin2.addSlider(_INTL("X:"), -64, 512 + 64, cel[AnimFrame::X])
  set2 = sliderwin2.addSlider(_INTL("Y:"), -64, 384 + 64, cel[AnimFrame::Y])
  set3 = sliderwin2.addSlider(_INTL("Zoom X:"), 5, 1000, cel[AnimFrame::ZOOMX])
  set4 = sliderwin2.addSlider(_INTL("Zoom Y:"), 5, 1000, cel[AnimFrame::ZOOMY])
  set5 = sliderwin2.addSlider(_INTL("Angle:"), 0, 359, cel[AnimFrame::ANGLE])
  set6 = sliderwin2.addSlider(_INTL("Opacity:"), 0, 255, cel[AnimFrame::OPACITY])
  set7 = sliderwin2.addSlider(_INTL("Blending:"), 0, 2, cel[AnimFrame::BLENDTYPE])
  set8 = sliderwin2.addTextSlider(_INTL("Flip:"), [_INTL("False"), _INTL("True")], cel[AnimFrame::MIRROR])
  prio = [_INTL("Back"), _INTL("Front"), _INTL("Below focus"), _INTL("Above focus"), _INTL("Between sides"), _INTL("Behind Pokémon")]
  set9 = sliderwin2.addTextSlider(_INTL("Priority:"), prio, cel[AnimFrame::PRIORITY] || 1)
  foc = [_INTL("User"), _INTL("Target"), _INTL("User and target"), _INTL("Screen")]
  curfoc = [3, 1, 0, 2, 3][cel[AnimFrame::FOCUS] || canvas.animation.position || 4]
  set10 = sliderwin2.addTextSlider(_INTL("Focus:"), foc, curfoc)
  flashbutton = sliderwin2.addButton(_INTL("Set Blending Color"))
  tonebutton = sliderwin2.addButton(_INTL("Set Color Tone"))
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  # Set X and Y for preview sprite
  cel[AnimFrame::X] = 320 + 96
  cel[AnimFrame::Y] = 96
  canvas.setSpriteBitmap(previewsprite, cel)
  pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
  previewsprite.z = previewwin.z + 1
  sliderwin2.opacity = 200
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(set0) ||
       sliderwin2.changed?(set3) ||
       sliderwin2.changed?(set4) ||
       sliderwin2.changed?(set5) ||
       sliderwin2.changed?(set6) ||
       sliderwin2.changed?(set7) ||
       sliderwin2.changed?(set8) ||
       sliderwin2.changed?(set9) ||
       sliderwin2.changed?(set10)
      # Update preview sprite
      cel[AnimFrame::PATTERN] = sliderwin2.value(set0) if set0 >= 0
      cel[AnimFrame::ZOOMX] = sliderwin2.value(set3)
      cel[AnimFrame::ZOOMY] = sliderwin2.value(set4)
      cel[AnimFrame::ANGLE] = sliderwin2.value(set5)
      cel[AnimFrame::OPACITY] = sliderwin2.value(set6)
      cel[AnimFrame::BLENDTYPE] = sliderwin2.value(set7)
      cel[AnimFrame::MIRROR] = sliderwin2.value(set8)
      cel[AnimFrame::PRIORITY] = sliderwin2.value(set9)
      cel[AnimFrame::FOCUS] = [2, 1, 3, 4][sliderwin2.value(set10)]
      canvas.setSpriteBitmap(previewsprite, cel)
      pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
      previewsprite.z = previewwin.z + 1
    end
    if sliderwin2.changed?(flashbutton)
      pbSetFlash(cel, previewsprite)
      pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
      previewsprite.z = previewwin.z + 1
    end
    if sliderwin2.changed?(tonebutton)
      pbSetTone(cel, previewsprite)
      pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
      previewsprite.z = previewwin.z + 1
    end
    if sliderwin2.changed?(okbutton)
      cel[AnimFrame::PATTERN] = sliderwin2.value(set0) if set0 >= 0
      cel[AnimFrame::X] = sliderwin2.value(set1)
      cel[AnimFrame::Y] = sliderwin2.value(set2)
      cel[AnimFrame::ZOOMX] = sliderwin2.value(set3)
      cel[AnimFrame::ZOOMY] = sliderwin2.value(set4)
      cel[AnimFrame::ANGLE] = sliderwin2.value(set5)
      cel[AnimFrame::OPACITY] = sliderwin2.value(set6)
      cel[AnimFrame::BLENDTYPE] = sliderwin2.value(set7)
      cel[AnimFrame::MIRROR] = sliderwin2.value(set8)
      cel[AnimFrame::PRIORITY] = sliderwin2.value(set9)
      cel[AnimFrame::FOCUS] = [2, 1, 3, 4][sliderwin2.value(set10)]
      thiscel = canvas.currentCel
      # Save by replacing current cell
      thiscel[0, thiscel.length] = cel
      break
    end
    if Input.trigger?(Input::A)
      commands = [
        _INTL("Pattern"),
        _INTL("X"),
        _INTL("Y"),
        _INTL("Zoom X"),
        _INTL("Zoom Y"),
        _INTL("Angle"),
        _INTL("Opacity"),
        _INTL("Blending"),
      ]
      cmdwin = pbListWindow(commands, 320)
      cmdwin.viewport = canvas.viewport
      cmdwin.height = 240
      cmdwin.z = 99999
      cmdwin.index = 0
      cmdwin.refresh
      Graphics.update
      loop do
        Graphics.update
        Input.update
        cmdwin.update
        if Input.trigger?(Input::C)
          case cmdwin.index
            when 0 # Copy
              params = ChooseNumberParams.new
              params.setRange(-2, 1000)
              params.setInitialValue(cel[AnimFrame::PATTERN])
              params.setCancelValue(cel[AnimFrame::PATTERN])
              cel[AnimFrame::PATTERN] = Kernel.pbMessageChooseNumber('Pattern Value:', params)
              sliderwin2.controls[set0].curvalue = cel[AnimFrame::PATTERN]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 1 # Paste
              params = ChooseNumberParams.new
              params.setRange(-64, 512 + 64)
              params.setInitialValue(sliderwin2.controls[set1].curvalue)
              params.setCancelValue(sliderwin2.controls[set2].curvalue)
              cel[AnimFrame::X] = Kernel.pbMessageChooseNumber('X Value:', params)
              sliderwin2.controls[set1].curvalue = cel[AnimFrame::X]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 2 # Clear Frame
              params = ChooseNumberParams.new
              params.setRange(-64, 512 + 64)
              params.setInitialValue(sliderwin2.controls[set2].curvalue)
              params.setCancelValue(sliderwin2.controls[set2].curvalue)
              cel[AnimFrame::Y] = Kernel.pbMessageChooseNumber('Y Value:', params)
              sliderwin2.controls[set2].curvalue = cel[AnimFrame::Y]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 3 # Insert Frame
              params = ChooseNumberParams.new
              params.setRange(5, 1000)
              params.setInitialValue(cel[AnimFrame::ZOOMX])
              params.setCancelValue(cel[AnimFrame::ZOOMX])
              cel[AnimFrame::ZOOMX] = Kernel.pbMessageChooseNumber('Zoom X Value:', params)
              sliderwin2.controls[set3].curvalue = cel[AnimFrame::ZOOMX]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 4 # Delete Frame
              params = ChooseNumberParams.new
              params.setRange(5, 1000)
              params.setInitialValue(cel[AnimFrame::ZOOMY])
              params.setCancelValue(cel[AnimFrame::ZOOMY])
              cel[AnimFrame::ZOOMY] = Kernel.pbMessageChooseNumber('Zoom Y Value:', params)
              sliderwin2.controls[set4].curvalue = cel[AnimFrame::ZOOMY]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 5 # Delete Frame
              params = ChooseNumberParams.new
              params.setRange(0, 359)
              params.setInitialValue(cel[AnimFrame::ANGLE])
              params.setCancelValue(cel[AnimFrame::ANGLE])
              cel[AnimFrame::ANGLE] = Kernel.pbMessageChooseNumber('Angle Value:', params)
              sliderwin2.controls[set5].curvalue = cel[AnimFrame::ANGLE]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 6 # Delete Frame
              params = ChooseNumberParams.new
              params.setRange(0, 255)
              params.setInitialValue(cel[AnimFrame::OPACITY])
              params.setCancelValue(cel[AnimFrame::OPACITY])
              cel[AnimFrame::OPACITY] = Kernel.pbMessageChooseNumber('Opacity Value:', params)
              sliderwin2.controls[set6].curvalue = cel[AnimFrame::OPACITY]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
            when 7 # Delete Frame
              params = ChooseNumberParams.new
              params.setRange(0, 2)
              params.setInitialValue(cel[AnimFrame::BLENDTYPE])
              params.setCancelValue(cel[AnimFrame::BLENDTYPE])
              cel[AnimFrame::BLENDTYPE] = Kernel.pbMessageChooseNumber('Blend Type Value:', params)
              sliderwin2.controls[set7].curvalue = cel[AnimFrame::BLENDTYPE]
              canvas.setSpriteBitmap(previewsprite, cel)
              pbSpriteSetAnimFrame(previewsprite, cel, nil, nil)
              previewsprite.z = previewwin.z + 1
              sliderwin2.update
              cmdwin.update
              next
          end
        end
        if Input.trigger?(Input::A)
          cmdwin.dispose
          sliderwin2.update
          Graphics.update
          Input.update
          break
        end
      end
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  previewwin.dispose
  previewsprite.dispose
  sliderwin2.dispose
  return
end

################################################################################
# Pop-up menus for buttons in right hand menu
################################################################################
def pbTimingList(canvas)
  commands = []
  cmdNewSound = -1
  cmdNewBG = -1
  cmdEditBG = -1
  cmdNewFO = -1
  cmdEditFO = -1
  for i in canvas.animation.timing
    commands.push(sprintf("%s", i))
  end
  commands[cmdNewSound = commands.length] = _INTL("Add: Play Sound...")
  commands[cmdNewBG = commands.length] = _INTL("Add: Set Background Graphic...")
  commands[cmdEditBG = commands.length] = _INTL("Add: Edit Background Color/Location...")
  commands[cmdNewFO = commands.length] = _INTL("Add: Set Foreground Graphic...")
  commands[cmdEditFO = commands.length] = _INTL("Add: Edit Foreground Color/Location...")
  cmdwin = pbListWindow(commands, 480)
  cmdwin.x = 0
  cmdwin.y = 0
  cmdwin.width = 640
  cmdwin.height = 384
  cmdwin.opacity = 200
  cmdwin.viewport = canvas.viewport
  framewindow = ControlWindow.new(0, 384, 640, 32 * 4)
  framewindow.addSlider(_INTL("Frame:"), 1, canvas.animation.length, canvas.currentframe + 1)
  framewindow.addButton(_INTL("Set Frame"))
  framewindow.addButton(_INTL("Delete Timing"))
  framewindow.opacity = 200
  framewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    framewindow.update
    if cmdwin.index != cmdNewSound &&
       cmdwin.index != cmdNewBG &&
       cmdwin.index != cmdEditBG &&
       cmdwin.index != cmdNewFO &&
       cmdwin.index != cmdEditFO
      if framewindow.changed?(1) # Set Frame
        canvas.animation.timing[cmdwin.index].frame = framewindow.value(0) - 1
        cmdwin.commands[cmdwin.index] = sprintf("%s", canvas.animation.timing[cmdwin.index])
        cmdwin.refresh
        next
      end
      if framewindow.changed?(2) # Delete Timing
        canvas.animation.timing.delete_at(cmdwin.index)
        cmdwin.commands.delete_at(cmdwin.index)
        cmdNewSound -= 1 if cmdNewSound >= 0
        cmdNewBG -= 1 if cmdNewBG >= 0
        cmdEditBG -= 1 if cmdEditBG >= 0
        cmdNewFO -= 1 if cmdNewFO >= 0
        cmdEditFO -= 1 if cmdEditFO >= 0
        cmdwin.refresh
        next
      end
    end
    if Input.trigger?(Input::C)
      redrawcmds = false
      if cmdwin.index == cmdNewSound # Add new sound
        newaudio = PBAnimTiming.new(0)
        if pbSelectSE(canvas, newaudio)
          newaudio.frame = framewindow.value(0) - 1
          canvas.animation.timing.push(newaudio)
          redrawcmds = true
        end
      elsif cmdwin.index == cmdNewBG # Add new background graphic set
        newtiming = PBAnimTiming.new(1)
        if pbSelectBG(canvas, newtiming)
          newtiming.frame = framewindow.value(0) - 1
          canvas.animation.timing.push(newtiming)
          redrawcmds = true
        end
      elsif cmdwin.index == cmdEditBG # Add new background edit
        newtiming = PBAnimTiming.new(2)
        if pbEditBG(canvas, newtiming)
          newtiming.frame = framewindow.value(0) - 1
          canvas.animation.timing.push(newtiming)
          redrawcmds = true
        end
      elsif cmdwin.index == cmdNewFO # Add new foreground graphic set
        newtiming = PBAnimTiming.new(3)
        if pbSelectBG(canvas, newtiming)
          newtiming.frame = framewindow.value(0) - 1
          canvas.animation.timing.push(newtiming)
          redrawcmds = true
        end
      elsif cmdwin.index == cmdEditFO # Add new foreground edit
        newtiming = PBAnimTiming.new(4)
        if pbEditBG(canvas, newtiming)
          newtiming.frame = framewindow.value(0) - 1
          canvas.animation.timing.push(newtiming)
          redrawcmds = true
        end
      else
        # Edit a timing here
        case canvas.animation.timing[cmdwin.index].timingType
          when 0
            pbSelectSE(canvas, canvas.animation.timing[cmdwin.index])
          when 1, 3
            pbSelectBG(canvas, canvas.animation.timing[cmdwin.index])
          when 2, 4
            pbEditBG(canvas, canvas.animation.timing[cmdwin.index])
        end
        cmdwin.commands[cmdwin.index] = sprintf("%s", canvas.animation.timing[cmdwin.index])
        cmdwin.refresh
      end
      if redrawcmds
        cmdwin.commands[cmdNewSound] = nil if cmdNewSound >= 0
        cmdwin.commands[cmdNewBG] = nil if cmdNewBG >= 0
        cmdwin.commands[cmdEditBG] = nil if cmdEditBG >= 0
        cmdwin.commands[cmdNewFO] = nil if cmdNewFO >= 0
        cmdwin.commands[cmdEditFO] = nil if cmdEditFO >= 0
        cmdwin.commands.compact!
        cmdwin.commands.push(sprintf("%s", canvas.animation.timing[canvas.animation.timing.length - 1]))
        cmdwin.commands[cmdNewSound = cmdwin.commands.length] = _INTL("Add: Play Sound...")
        cmdwin.commands[cmdNewBG = cmdwin.commands.length] = _INTL("Add: Set Background Graphic...")
        cmdwin.commands[cmdEditBG = cmdwin.commands.length] = _INTL("Add: Edit Background Color/Location...")
        cmdwin.commands[cmdNewFO = cmdwin.commands.length] = _INTL("Add: Set Foreground Graphic...")
        cmdwin.commands[cmdEditFO = cmdwin.commands.length] = _INTL("Add: Edit Foreground Color/Location...")
        cmdwin.refresh
      end
    elsif Input.trigger?(Input::B)
      break
    end
  end
  cmdwin.dispose
  framewindow.dispose
  return
end

def pbSelectSE(canvas, audio)
  filename = (audio.name != "") ? audio.name : ""
  displayname = (filename != "") ? filename : _INTL("<user's cry>")
  cmdNone = -1
  animfiles = []
  animfiles[cmdNone = animfiles.length] = _INTL("[Play user's cry]")
  ret = false
  pbRgssChdir("./Audio/SE/") {
    animfiles.concat(Dir.glob("*.wav"))
    animfiles.concat(Dir.glob("*.mp3"))
    animfiles.concat(Dir.glob("*.ogg"))
    animfiles.concat(Dir.glob("*.wma"))
  }
  animfiles = animfiles.sort_by { |name|
    name.include?("[Play user's cry]") ? 0 : (name.include?(".wav") ? 1 : (name.include?(".mp3") ? 2 : (name.include?(".ogg") ? 3 : 4)))
  }
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.height = 480
  cmdwin.opacity = 200
  cmdwin.viewport = canvas.viewport
  maxsizewindow = ControlWindow.new(320, 0, 320, 32 * 8)
  maxsizewindow.addLabel(_INTL("File: \"{1}\"", displayname))
  maxsizewindow.addSlider(_INTL("Volume:"), 0, 100, audio.volume)
  maxsizewindow.addSlider(_INTL("Pitch:"), 20, 250, audio.pitch)
  maxsizewindow.addButton(_INTL("Play Sound"))
  maxsizewindow.addButton(_INTL("Stop Sound"))
  maxsizewindow.addButton(_INTL("OK"))
  maxsizewindow.addButton(_INTL("Cancel"))
  maxsizewindow.opacity = 200
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    if maxsizewindow.changed?(3) && animfiles.length > 0 # Play Sound
      pbSEPlay(RPG::AudioFile.new(filename, maxsizewindow.value(1), maxsizewindow.value(2)))
    end
    if maxsizewindow.changed?(4) && animfiles.length > 0 # Stop Sound
      pbSEStop()
    end
    if maxsizewindow.changed?(5) # OK
      audio.name = filename
      audio.volume = maxsizewindow.value(1)
      audio.pitch = maxsizewindow.value(2)
      ret = true
      break
    end
    if maxsizewindow.changed?(6) # Cancel
      break
    end

    if Input.trigger?(Input::C) && animfiles.length > 0
      filename = (cmdwin.index == cmdNone) ? "" : cmdwin.commands[cmdwin.index]
      displayname = (filename != "") ? filename : _INTL("<user's cry>")
      maxsizewindow.controls[0].text = _INTL("File: \"{1}\"", displayname)
    elsif Input.trigger?(Input::B)
      break
    elsif Input.trigger?(Input::Y)
      maxsizewindow.dispose
      ret = pbsearchSE(canvas, audio, animfiles)
      break
    end
  end
  cmdwin.dispose
  maxsizewindow.dispose
  return ret
end

def pbsearchSE(canvas, audio, animfiles)
  window = ControlWindow.new(320, 128, 320, 32 * 4)
  window.z = 99999
  window.addControl(TextField.new(_INTL("Search for:"), ""))
  okbutton = window.addButton(_INTL("OK"))
  cancelbutton = window.addButton(_INTL("Cancel"))
  window.opacity = 256
  breaking_off = false
  searchname = nil
  Input.text_input = true
  loop do
    Graphics.update
    Input.update
    window.update
    if window.changed?(okbutton) || Input.triggerex?(:RETURN)
      searchname = window.controls[0].text
      break
    end
    if window.changed?(cancelbutton) || Input.triggerex?(:ESCAPE)
      breaking_off = true
      break
    end
  end
  Input.text_input = false
  if breaking_off
    window.dispose
    return false
  end
  window.dispose
  hassearchterm = []
  newanimations = []
  searchnameupcase = searchname.upcase
  for i in 0...animfiles.length
    upcasednames = animfiles[i].upcase
    if upcasednames.include?(searchnameupcase)
      newanimations.push(animfiles[i])
    end
  end
  if newanimations.length == 0
    Kernel.pbMessage("no results!")
    return false
  end
  animfiles = newanimations
  cmdNone = 0
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.height = 480
  cmdwin.opacity = 200
  cmdwin.viewport = canvas.viewport
  cmdwin.index = 0
  ret = false
  filename = cmdwin.commands[cmdwin.index]
  displayname = (filename != "") ? filename : _INTL("<user's cry>")
  maxsizewindow = ControlWindow.new(320, 0, 320, 32 * 8)
  maxsizewindow.addLabel(_INTL("File: \"{1}\"", displayname))
  maxsizewindow.addSlider(_INTL("Volume:"), 0, 100, audio.volume)
  maxsizewindow.addSlider(_INTL("Pitch:"), 20, 250, audio.pitch)
  maxsizewindow.addButton(_INTL("Play Sound"))
  maxsizewindow.addButton(_INTL("Stop Sound"))
  maxsizewindow.addButton(_INTL("OK"))
  maxsizewindow.addButton(_INTL("Cancel"))
  maxsizewindow.opacity = 200
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    if maxsizewindow.changed?(3) && animfiles.length > 0 # Play Sound
      pbSEPlay(RPG::AudioFile.new(filename, maxsizewindow.value(1), maxsizewindow.value(2)))
    end
    if maxsizewindow.changed?(4) && animfiles.length > 0 # Stop Sound
      pbSEStop()
    end
    if maxsizewindow.changed?(5) # OK
      audio.name = filename
      audio.volume = maxsizewindow.value(1)
      audio.pitch = maxsizewindow.value(2)
      ret = true
      break
    end
    if maxsizewindow.changed?(6) # Cancel
      break
    end

    if Input.trigger?(Input::C) && animfiles.length > 0
      filename = cmdwin.commands[cmdwin.index]
      displayname = (filename != "") ? filename : _INTL("<user's cry>")
      maxsizewindow.controls[0].text = _INTL("File: \"{1}\"", displayname)
    elsif Input.trigger?(Input::B)
      break
    end
  end
  cmdwin.dispose
  maxsizewindow.dispose

  return ret
end

def pbSelectBG(canvas, timing)
  filename = timing.name
  cmdErase = 0
  animfiles = []
  # animfiles[cmdErase=animfiles.length]=_INTL("[Erase background graphic]")
  ret = false
  pbRgssChdir("./Graphics/Animations/") {
    animfiles.concat(Dir.glob("*.bmp"))
    animfiles.concat(Dir.glob("*.png"))
    animfiles.concat(Dir.glob("*.jpg"))
    animfiles.concat(Dir.glob("*.jpeg"))
    animfiles.concat(Dir.glob("*.gif"))
  }
  animfiles.sort!
  animfiles.unshift(_INTL "[Erase background graphic]")
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.height = 480
  cmdwin.opacity = 200
  cmdwin.viewport = canvas.viewport
  maxsizewindow = ControlWindow.new(320, 0, 320, 32 * 11)
  maxsizewindow.addLabel(_INTL("File: \"{1}\"", filename))
  maxsizewindow.addSlider(_INTL("X:"), -5000, 5000, timing.bgX || 0)
  maxsizewindow.addSlider(_INTL("Y:"), -5000, 5000, timing.bgY || 0)
  maxsizewindow.addSlider(_INTL("Opacity:"), 0, 255, timing.opacity || 0)
  maxsizewindow.addSlider(_INTL("Red:"), 0, 255, timing.colorRed || 0)
  maxsizewindow.addSlider(_INTL("Green:"), 0, 255, timing.colorGreen || 0)
  maxsizewindow.addSlider(_INTL("Blue:"), 0, 255, timing.colorBlue || 0)
  maxsizewindow.addSlider(_INTL("Alpha:"), 0, 255, timing.colorAlpha || 0)
  maxsizewindow.addButton(_INTL("OK"))
  maxsizewindow.addButton(_INTL("Cancel"))
  maxsizewindow.opacity = 200
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    if maxsizewindow.changed?(8) # OK
      timing.name = filename
      timing.bgX = maxsizewindow.value(1)
      timing.bgY = maxsizewindow.value(2)
      timing.opacity = maxsizewindow.value(3)
      timing.colorRed = maxsizewindow.value(4)
      timing.colorGreen = maxsizewindow.value(5)
      timing.colorBlue = maxsizewindow.value(6)
      timing.colorAlpha = maxsizewindow.value(7)
      ret = true
      break
    end
    if maxsizewindow.changed?(9) # Cancel
      break
    end

    if Input.trigger?(Input::C) && animfiles.length > 0
      filename = (cmdwin.index == cmdErase) ? "" : cmdwin.commands[cmdwin.index]
      maxsizewindow.controls[0].text = _INTL("File: \"{1}\"", filename)
    elsif Input.trigger?(Input::B)
      break
    elsif Input.trigger?(Input::Y)
      maxsizewindow.dispose
      ret = pbSearchBG(animfiles, canvas, timing)
      break
    end
  end
  cmdwin.dispose
  maxsizewindow.dispose
  return ret
end

def pbSearchBG(animfiles, canvas, timing)
  window = ControlWindow.new(320, 128, 320, 32 * 4)
  window.z = 99999
  window.addControl(TextField.new(_INTL("Search for:"), ""))
  okbutton = window.addButton(_INTL("OK"))
  cancelbutton = window.addButton(_INTL("Cancel"))
  window.opacity = 256
  breaking_off = false
  searchname = nil
  Input.text_input = true
  loop do
    Graphics.update
    Input.update
    window.update
    if window.changed?(okbutton) || Input.triggerex?(:RETURN)
      searchname = window.controls[0].text
      break
    end
    if window.changed?(cancelbutton) || Input.triggerex?(:ESCAPE)
      breaking_off = true
      break
    end
  end
  Input.text_input = false
  if breaking_off
    window.dispose
    return false
  end
  window.dispose
  hassearchterm = []
  newanimations = []
  searchnameupcase = searchname.upcase
  for i in 0...animfiles.length
    upcasednames = animfiles[i].upcase
    if upcasednames.include?(searchnameupcase)
      newanimations.push(animfiles[i])
    end
  end
  if newanimations.length == 0
    Kernel.pbMessage("no results!")
    return false
  end
  animfiles = newanimations
  cmdErase = 0
  ret = false
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.index = 0
  cmdwin.height = 480
  cmdwin.opacity = 200
  cmdwin.viewport = canvas.viewport
  filename = cmdwin.commands[cmdwin.index]
  maxsizewindow = ControlWindow.new(320, 0, 320, 32 * 11)
  maxsizewindow.addLabel(_INTL("File: \"{1}\"", filename))
  maxsizewindow.addSlider(_INTL("X:"), -5000, 5000, timing.bgX || 0)
  maxsizewindow.addSlider(_INTL("Y:"), -5000, 5000, timing.bgY || 0)
  maxsizewindow.addSlider(_INTL("Opacity:"), 0, 255, timing.opacity || 0)
  maxsizewindow.addSlider(_INTL("Red:"), 0, 255, timing.colorRed || 0)
  maxsizewindow.addSlider(_INTL("Green:"), 0, 255, timing.colorGreen || 0)
  maxsizewindow.addSlider(_INTL("Blue:"), 0, 255, timing.colorBlue || 0)
  maxsizewindow.addSlider(_INTL("Alpha:"), 0, 255, timing.colorAlpha || 0)
  maxsizewindow.addButton(_INTL("OK"))
  maxsizewindow.addButton(_INTL("Cancel"))
  maxsizewindow.opacity = 200
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    if maxsizewindow.changed?(8) # OK
      timing.name = filename
      timing.bgX = maxsizewindow.value(1)
      timing.bgY = maxsizewindow.value(2)
      timing.opacity = maxsizewindow.value(3)
      timing.colorRed = maxsizewindow.value(4)
      timing.colorGreen = maxsizewindow.value(5)
      timing.colorBlue = maxsizewindow.value(6)
      timing.colorAlpha = maxsizewindow.value(7)
      ret = true
      break
    end
    if maxsizewindow.changed?(9) # Cancel
      break
    end

    if Input.trigger?(Input::C) && animfiles.length > 0
      filename = cmdwin.commands[cmdwin.index]
      maxsizewindow.controls[0].text = _INTL("File: \"{1}\"", filename)
    elsif Input.trigger?(Input::B)
      break
    end
  end
  cmdwin.dispose
  maxsizewindow.dispose
  return ret
end

def pbEditBG(canvas, timing)
  ret = false
  maxsizewindow = ControlWindow.new(0, 0, 320, 32 * 11)
  maxsizewindow.addSlider(_INTL("Duration:"), 0, 100, timing.duration)
  maxsizewindow.addOptionalSlider(_INTL("X:"), -5000, 5000, timing.bgX || 0)
  maxsizewindow.addOptionalSlider(_INTL("Y:"), -5000, 5000, timing.bgY || 0)
  maxsizewindow.addOptionalSlider(_INTL("Opacity:"), 0, 255, timing.opacity || 0)
  maxsizewindow.addOptionalSlider(_INTL("Red:"), 0, 255, timing.colorRed || 0)
  maxsizewindow.addOptionalSlider(_INTL("Green:"), 0, 255, timing.colorGreen || 0)
  maxsizewindow.addOptionalSlider(_INTL("Blue:"), 0, 255, timing.colorBlue || 0)
  maxsizewindow.addOptionalSlider(_INTL("Alpha:"), 0, 255, timing.colorAlpha || 0)
  maxsizewindow.addButton(_INTL("OK"))
  maxsizewindow.addButton(_INTL("Cancel"))
  maxsizewindow.controls[1].checked = (timing.bgX != nil)
  maxsizewindow.controls[2].checked = (timing.bgY != nil)
  maxsizewindow.controls[3].checked = (timing.opacity != nil)
  maxsizewindow.controls[4].checked = (timing.colorRed != nil)
  maxsizewindow.controls[5].checked = (timing.colorGreen != nil)
  maxsizewindow.controls[6].checked = (timing.colorBlue != nil)
  maxsizewindow.controls[7].checked = (timing.colorAlpha != nil)
  maxsizewindow.opacity = 200
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    maxsizewindow.update
    if maxsizewindow.changed?(8) # OK
      if maxsizewindow.controls[1].checked ||
         maxsizewindow.controls[2].checked ||
         maxsizewindow.controls[3].checked ||
         maxsizewindow.controls[4].checked ||
         maxsizewindow.controls[5].checked ||
         maxsizewindow.controls[6].checked ||
         maxsizewindow.controls[7].checked
        timing.duration = maxsizewindow.value(0)
        timing.bgX = maxsizewindow.value(1)
        timing.bgY = maxsizewindow.value(2)
        timing.opacity = maxsizewindow.value(3)
        timing.colorRed = maxsizewindow.value(4)
        timing.colorGreen = maxsizewindow.value(5)
        timing.colorBlue = maxsizewindow.value(6)
        timing.colorAlpha = maxsizewindow.value(7)
        ret = true
        break
      else
        break
      end
    end
    if maxsizewindow.changed?(9) # Cancel
      break
    end
    if Input.trigger?(Input::B)
      break
    end
  end
  maxsizewindow.dispose
  return ret
end

def pbCopyCels(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 300, 32 * 19)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.opacity = 200
  s1set0 = sliderwin2.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin2.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin2.addSlider(_INTL("Copy From:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set3 = sliderwin2.addSlider(_INTL("Copy To:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set4 = sliderwin2.addSlider(_INTL("Frame Offset:"), 0 - canvas.animation.length, canvas.animation.length, 0)
  set0 = sliderwin2.addCheckbox(_INTL("Pattern"))
  set1 = sliderwin2.addCheckbox(_INTL("Position"))
  set2 = sliderwin2.addCheckbox(_INTL("Zoom"))
  set3 = sliderwin2.addCheckbox(_INTL("Angle"))
  set4 = sliderwin2.addCheckbox(_INTL("Opacity"))
  set5 = sliderwin2.addCheckbox(_INTL("Blending"))
  set6 = sliderwin2.addCheckbox(_INTL("Mirror"))
  set7 = sliderwin2.addCheckbox(_INTL("Priority"))
  set8 = sliderwin2.addCheckbox(_INTL("Focus"))
  set9 = sliderwin2.addCheckbox(_INTL("Blend Color"))
  set10 = sliderwin2.addCheckbox(_INTL("Color Tone"))
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin2.value(s1set0) - 1
      endframe = sliderwin2.value(s1set1) - 1
      origincel = sliderwin2.value(s1set2)
      targetcel = sliderwin2.value(s1set3)
      clonearray = [] # Used so cells don't get overwritten mid-copy
      count = 0 # To find position in clone array
      for i in startframe..endframe
        clonecell = canvas.animation[i][origincel]
        clonecell = clonecell.clone
        clonearray.push(clonecell)
      end
      for i in startframe..endframe
        offset = sliderwin2.value(s1set4) if s1set3
        offset = 0 if !offset
        next if (i + offset) < 0 || (i + offset) > (canvas.animation.length - 1)

        copycel = clonearray[count]
        pastecel = canvas.animation[i + offset][targetcel]
        next if !copycel || !pastecel

        pastecel[AnimFrame::PATTERN] = copycel[AnimFrame::PATTERN] if sliderwin2.value(set0)
        if sliderwin2.value(set1)
          pastecel[AnimFrame::X] = copycel[AnimFrame::X]
          pastecel[AnimFrame::Y] = copycel[AnimFrame::Y]
        end
        if sliderwin2.value(set2)
          pastecel[AnimFrame::ZOOMX] = copycel[AnimFrame::ZOOMX]
          pastecel[AnimFrame::ZOOMY] = copycel[AnimFrame::ZOOMY]
        end
        pastecel[AnimFrame::ANGLE] = copycel[AnimFrame::ANGLE] if sliderwin2.value(set3)
        pastecel[AnimFrame::OPACITY] = copycel[AnimFrame::OPACITY] if sliderwin2.value(set4)
        pastecel[AnimFrame::BLENDTYPE] = copycel[AnimFrame::BLENDTYPE] if sliderwin2.value(set5)
        pastecel[AnimFrame::MIRROR] = copycel[AnimFrame::MIRROR] if sliderwin2.value(set6)
        pastecel[AnimFrame::PRIORITY] = copycel[AnimFrame::PRIORITY] if sliderwin2.value(set7)
        pastecel[AnimFrame::FOCUS] = copycel[AnimFrame::FOCUS] if sliderwin2.value(set8)
        if sliderwin2.value(set9)
          pastecel[AnimFrame::COLORRED] = copycel[AnimFrame::COLORRED]
          pastecel[AnimFrame::COLORGREEN] = copycel[AnimFrame::COLORGREEN]
          pastecel[AnimFrame::COLORBLUE] = copycel[AnimFrame::COLORBLUE]
          pastecel[AnimFrame::COLORALPHA] = copycel[AnimFrame::COLORALPHA]
        end
        if sliderwin2.value(set10)
          pastecel[AnimFrame::TONERED] = copycel[AnimFrame::TONERED]
          pastecel[AnimFrame::TONEGREEN] = copycel[AnimFrame::TONEGREEN]
          pastecel[AnimFrame::TONEBLUE] = copycel[AnimFrame::TONEBLUE]
          pastecel[AnimFrame::TONEGRAY] = copycel[AnimFrame::TONEGRAY]
        end
        count += 1
      end
      canvas.invalidate
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbCopyFrames(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 6)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  sliderwin2.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  sliderwin2.addSlider(_INTL("Copy to:"), 1, canvas.animation.length, canvas.currentframe + 1)
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  sliderwin2.opacity = 200
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      startvalue = sliderwin2.value(0) - 1
      endvalue = sliderwin2.value(1) - 1
      dstvalue = sliderwin2.value(2) - 1
      length = (endvalue - startvalue) + 1
      if length > 0 # Ensure correct overlap handling
        if (startvalue < dstvalue)
          startvalue += length
          dstvalue += length
          while length != 0
            canvas.copyFrame(startvalue - 1, dstvalue - 1)
            startvalue -= 1
            dstvalue -= 1
            length -= 1
          end
        elsif startvalue != dstvalue
          while length != 0
            canvas.copyFrame(startvalue, dstvalue)
            startvalue += 1
            dstvalue += 1
            length -= 1
          end
        end
      end
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbClearFrames(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 5)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  sliderwin2.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  sliderwin2.opacity = 200
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      startframe = sliderwin2.value(0) - 1
      endframe = sliderwin2.value(1) - 1
      for i in startframe..endframe
        canvas.clearFrame(i)
      end
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbTweening(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 10)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.opacity = 200
  s1set0 = sliderwin2.addSlider(_INTL("Starting Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin2.addSlider(_INTL("Ending Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin2.addSlider(_INTL("First Cel:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set3 = sliderwin2.addSlider(_INTL("Last Cel:"), 0, PBAnimation::MAXSPRITES - 1, PBAnimation::MAXSPRITES - 1)
  set0 = sliderwin2.addCheckbox(_INTL("Pattern"))
  set1 = sliderwin2.addCheckbox(_INTL("Position/Zoom/Angle"))
  set2 = sliderwin2.addCheckbox(_INTL("Opacity/Blending"))
  set3 = sliderwin2.addCheckbox(_INTL("Tone/Colour"))
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin2.value(s1set0) - 1
      endframe = sliderwin2.value(s1set1) - 1
      break if startframe >= endframe

      frames = endframe - startframe
      startcel = sliderwin2.value(s1set2)
      endcel = sliderwin2.value(s1set3)
      for j in startcel..endcel
        cel1 = canvas.animation[startframe][j]
        cel2 = canvas.animation[endframe][j]
        next if !cel1 || !cel2

        diffPattern = cel2[AnimFrame::PATTERN] - cel1[AnimFrame::PATTERN]
        diffX = cel2[AnimFrame::X] - cel1[AnimFrame::X]
        diffY = cel2[AnimFrame::Y] - cel1[AnimFrame::Y]
        diffZoomX = cel2[AnimFrame::ZOOMX] - cel1[AnimFrame::ZOOMX]
        diffZoomY = cel2[AnimFrame::ZOOMY] - cel1[AnimFrame::ZOOMY]
        diffAngle = cel2[AnimFrame::ANGLE] - cel1[AnimFrame::ANGLE]
        diffOpacity = cel2[AnimFrame::OPACITY] - cel1[AnimFrame::OPACITY]
        diffBlend = cel2[AnimFrame::BLENDTYPE] - cel1[AnimFrame::BLENDTYPE]
        diffRedTone = cel2[AnimFrame::TONERED] - cel1[AnimFrame::TONERED]
        diffBlueTone = cel2[AnimFrame::TONEBLUE] - cel1[AnimFrame::TONEBLUE]
        diffGreenTone = cel2[AnimFrame::TONEGREEN] - cel1[AnimFrame::TONEGREEN]
        diffGrayTone = cel2[AnimFrame::TONEGRAY] - cel1[AnimFrame::TONEGRAY]
        diffRedColor = cel2[AnimFrame::COLORRED] - cel1[AnimFrame::COLORRED]
        diffBlueColor = cel2[AnimFrame::COLORBLUE] - cel1[AnimFrame::COLORBLUE]
        diffGreenColor = cel2[AnimFrame::COLORGREEN] - cel1[AnimFrame::COLORGREEN]
        diffAlphaColor = cel2[AnimFrame::COLORALPHA] - cel1[AnimFrame::COLORALPHA]
        startPattern = cel1[AnimFrame::PATTERN]
        startX = cel1[AnimFrame::X]
        startY = cel1[AnimFrame::Y]
        startZoomX = cel1[AnimFrame::ZOOMX]
        startZoomY = cel1[AnimFrame::ZOOMY]
        startAngle = cel1[AnimFrame::ANGLE]
        startOpacity = cel1[AnimFrame::OPACITY]
        startBlend = cel1[AnimFrame::BLENDTYPE]
        startRedTone = cel1[AnimFrame::TONERED]
        startBlueTone = cel1[AnimFrame::TONEBLUE]
        startGreenTone = cel1[AnimFrame::TONEGREEN]
        startGrayTone = cel1[AnimFrame::TONEGRAY]
        startRedColor = cel1[AnimFrame::COLORRED]
        startBlueColor = cel1[AnimFrame::COLORBLUE]
        startGreenColor = cel1[AnimFrame::COLORGREEN]
        startAlphaColor = cel1[AnimFrame::COLORALPHA]
        for k in 0..frames
          cel = canvas.animation[startframe + k][j]
          curcel = cel
          if !cel
            cel = pbCreateCel(0, 0, 0)
            canvas.animation[startframe + k][j] = cel
          end
          if sliderwin2.value(set0) || !curcel
            cel[AnimFrame::PATTERN] = startPattern + (diffPattern * k / frames)
          end
          if sliderwin2.value(set1) || !curcel
            cel[AnimFrame::X] = startX + (diffX * k / frames)
            cel[AnimFrame::Y] = startY + (diffY * k / frames)
            cel[AnimFrame::ZOOMX] = startZoomX + (diffZoomX * k / frames)
            cel[AnimFrame::ZOOMY] = startZoomY + (diffZoomY * k / frames)
            cel[AnimFrame::ANGLE] = startAngle + (diffAngle * k / frames)
          end
          if sliderwin2.value(set2) || !curcel
            cel[AnimFrame::OPACITY] = startOpacity + (diffOpacity * k / frames)
            cel[AnimFrame::BLENDTYPE] = startBlend + (diffBlend * k / frames)
          end
          if sliderwin2.value(set3) # || !curcel # I think this needs to be commented?
            cel[AnimFrame::TONERED] = startRedTone + (diffRedTone * k / frames)
            cel[AnimFrame::TONEBLUE] = startBlueTone + (diffBlueTone * k / frames)
            cel[AnimFrame::TONEGREEN] = startGreenTone + (diffGreenTone * k / frames)
            cel[AnimFrame::TONEGRAY] = startGrayTone + (diffGrayTone * k / frames)
            cel[AnimFrame::COLORRED] = startRedColor + (diffRedColor * k / frames)
            cel[AnimFrame::COLORBLUE] = startBlueColor + (diffBlueColor * k / frames)
            cel[AnimFrame::COLORGREEN] = startGreenColor + (diffGreenColor * k / frames)
            cel[AnimFrame::COLORALPHA] = startAlphaColor + (diffAlphaColor * k / frames)
          end
          cel[AnimFrame::FOCUS] = cel1[AnimFrame::FOCUS]
        end
      end
      canvas.invalidate
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
end

def pbReverseAnim(canvas)
  sliderwin1 = ControlWindow.new(0, 0, 300, 32 * 5)
  sliderwin1.viewport = canvas.viewport
  sliderwin1.opacity = 200
  s1set0 = sliderwin1.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin1.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  okbutton = sliderwin1.addButton(_INTL("OK"))
  cancelbutton = sliderwin1.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin1.update
    if sliderwin1.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin1.value(s1set0) - 1
      endframe = sliderwin1.value(s1set1) - 1
      if startframe > endframe
        startframe = sliderwin1.value(s1set1) - 1
        endframe = sliderwin1.value(s1set0) - 1
      end
      midpoint = endframe - ((endframe - startframe) / 2)
      for i in startframe..endframe
        break if (i > midpoint || i == midpoint)

        count = i - startframe
        canvas.copyFrame(i, endframe - count, true)
      end
      canvas.invalidate
      break
    end
    if sliderwin1.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin1.dispose
end

def pbConvertSelfTarget(canvas)
  confirmwin = ControlWindow.new(0, 0, 325, 32 * 7)
  confirmwin.viewport = canvas.viewport
  confirmwin.opacity = 200
  confirmwin.addLabel(_INTL("This function is for converting"))
  confirmwin.addLabel(_INTL("self-moves that have the user's"))
  confirmwin.addLabel(_INTL("effects on the target sprite."))
  confirmwin.addLabel(_INTL("Continue?"))
  # Add a checkbox here to move all other cels -256,+128 if necessary for anything, don't think it is but the old code was such a clusterfuck I have no clue anymore
  okbutton = confirmwin.addButton(_INTL("Yes"))
  cancelbutton = confirmwin.addButton(_INTL("No"))
  loop do
    Graphics.update
    Input.update
    confirmwin.update
    if confirmwin.changed?(okbutton) || Input.trigger?(Input::C)
      for frame in 0..canvas.animation.length - 1
        usercel = canvas.animation[frame][0]
        targetcel = canvas.animation[frame][1]
        # Setting user effects to target effects
        usercel[AnimFrame::X] = (targetcel[AnimFrame::X] - 256)
        usercel[AnimFrame::Y] = (targetcel[AnimFrame::Y] + 128)
        usercel[AnimFrame::ZOOMX] = targetcel[AnimFrame::ZOOMX]
        usercel[AnimFrame::ZOOMY] = targetcel[AnimFrame::ZOOMY]
        usercel[AnimFrame::ANGLE] = targetcel[AnimFrame::ANGLE]
        usercel[AnimFrame::OPACITY] = targetcel[AnimFrame::OPACITY]
        usercel[AnimFrame::BLENDTYPE] = targetcel[AnimFrame::BLENDTYPE]
        usercel[AnimFrame::MIRROR] = targetcel[AnimFrame::MIRROR]
        usercel[AnimFrame::PRIORITY] = targetcel[AnimFrame::PRIORITY]
        usercel[AnimFrame::FOCUS] = targetcel[AnimFrame::FOCUS]
        usercel[AnimFrame::COLORRED] = targetcel[AnimFrame::COLORRED]
        usercel[AnimFrame::COLORGREEN] = targetcel[AnimFrame::COLORGREEN]
        usercel[AnimFrame::COLORBLUE] = targetcel[AnimFrame::COLORBLUE]
        usercel[AnimFrame::COLORALPHA] = targetcel[AnimFrame::COLORALPHA]
        usercel[AnimFrame::TONERED] = targetcel[AnimFrame::TONERED]
        usercel[AnimFrame::TONEGREEN] = targetcel[AnimFrame::TONEGREEN]
        usercel[AnimFrame::TONEBLUE] = targetcel[AnimFrame::TONEBLUE]
        usercel[AnimFrame::TONEGRAY] = targetcel[AnimFrame::TONEGRAY]
        # Resetting target to original settings
        targetcel[AnimFrame::X] = 384
        targetcel[AnimFrame::Y] = 96
        pbResetCel(targetcel)
        # This is where moving other cels will go if needed
      end
      canvas.invalidate
      break
    end
    if confirmwin.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  confirmwin.dispose
end

def pbFlipAroundPoint(canvas)
  sliderwin1 = ControlWindow.new(0, 0, 300, 32 * 11)
  sliderwin1.viewport = canvas.viewport
  sliderwin1.opacity = 200
  s1set0 = sliderwin1.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin1.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin1.addSlider(_INTL("First Cel:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set3 = sliderwin1.addSlider(_INTL("Last Cel:"), 0, PBAnimation::MAXSPRITES - 1, PBAnimation::MAXSPRITES - 1)
  s1set4 = sliderwin1.addSlider(_INTL("X Flip Point:"), -64, 512 + 64, 384)
  s1set5 = sliderwin1.addSlider(_INTL("Y Flip Point:"), -64, 384 + 64, 96)
  set6 = sliderwin1.addCheckbox(_INTL("Flip Vertical"))
  set7 = sliderwin1.addCheckbox(_INTL("Flip Horizontal"))
  okbutton = sliderwin1.addButton(_INTL("OK"))
  cancelbutton = sliderwin1.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin1.update
    if sliderwin1.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin1.value(s1set0) - 1
      endframe = sliderwin1.value(s1set1) - 1
      startcel = sliderwin1.value(s1set2)
      endcel = sliderwin1.value(s1set3)
      xflip = sliderwin1.value(s1set4)
      yflip = sliderwin1.value(s1set5)
      for i in startframe..endframe
        for j in startcel..endcel
          next if !canvas.animation[i][j]

          cel = canvas.animation[i][j]
          if sliderwin1.value(set7)
            xflipnum = xflip - cel[AnimFrame::X]
            cel[AnimFrame::X] = xflip + xflipnum
          end
          if sliderwin1.value(6)
            yflipnum = yflip - cel[AnimFrame::Y]
            cel[AnimFrame::Y] = yflip + yflipnum
          end
        end
      end
      canvas.invalidate
      break
    end
    if sliderwin1.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin1.dispose
end

def pbCelRotateFlip(canvas)
  sliderwin1 = ControlWindow.new(0, 0, 300, 32 * 7)
  sliderwin1.viewport = canvas.viewport
  sliderwin1.opacity = 200
  s1set0 = sliderwin1.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin1.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin1.addSlider(_INTL("First Cel:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set3 = sliderwin1.addSlider(_INTL("Last Cel:"), 0, PBAnimation::MAXSPRITES - 1, PBAnimation::MAXSPRITES - 1)
  okbutton = sliderwin1.addButton(_INTL("OK"))
  cancelbutton = sliderwin1.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin1.update
    if sliderwin1.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin1.value(s1set0) - 1
      endframe = sliderwin1.value(s1set1) - 1
      startcel = sliderwin1.value(s1set2)
      endcel = sliderwin1.value(s1set3)
      for i in startframe..endframe
        for j in startcel..endcel
          next if !canvas.animation[i][j]

          cel = canvas.animation[i][j]
          cel[AnimFrame::ANGLE] = 360 - cel[AnimFrame::ANGLE]
        end
      end
      canvas.invalidate
      break
    end
    if sliderwin1.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin1.dispose
end

def pbRenumberBatch(canvas)
  sliderwin1 = ControlWindow.new(0, 0, 300, 32 * 7)
  sliderwin1.viewport = canvas.viewport
  sliderwin1.opacity = 200
  s1set0 = sliderwin1.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin1.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin1.addSlider(_INTL("Old Cel Number:"), 2, 99, 2)
  s1set3 = sliderwin1.addSlider(_INTL("New Cel Number:"), 2, 99, 2)
  okbutton = sliderwin1.addButton(_INTL("OK"))
  cancelbutton = sliderwin1.addButton(_INTL("Cancel"))
  loop do
    Graphics.update
    Input.update
    sliderwin1.update
    if sliderwin1.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin1.value(s1set0) - 1
      endframe = sliderwin1.value(s1set1) - 1
      cel1 = sliderwin1.value(s1set2)
      cel2 = sliderwin1.value(s1set3)
      for i in startframe..endframe
        canvas.swapCels(cel1, cel2, i)
      end
      canvas.invalidate
      break
    end
    if sliderwin1.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin1.dispose
end

def pbCellBatch(canvas)
  sliderwin1 = ControlWindow.new(0, 0, 280, 32 * 5)
  sliderwin1.viewport = canvas.viewport
  sliderwin1.opacity = 200
  s1set0 = sliderwin1.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  s1set1 = sliderwin1.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  s1set2 = sliderwin1.addSlider(_INTL("First Cel:"), 0, PBAnimation::MAXSPRITES - 1, 0)
  s1set3 = sliderwin1.addSlider(_INTL("Last Cel:"), 0, PBAnimation::MAXSPRITES - 1, PBAnimation::MAXSPRITES - 1)
  sliderwin2 = ControlWindow.new(280, 0, 360, 32 * 17)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.opacity = 200
  set0 = sliderwin2.addOptionalSlider(_INTL("Pattern:"), -2, 1000, 0)
  set11 = sliderwin2.addOptionalSlider(_INTL("Increase Pattern:"), -1000, 1000, 0)
  set1 = sliderwin2.addOptionalSlider(_INTL("X:"), -64, 512 + 64, 0)
  set2 = sliderwin2.addOptionalSlider(_INTL("Y:"), -64, 384 + 64, 0)
  set3 = sliderwin2.addOptionalSlider(_INTL("Zoom X:"), 5, 1000, 100)
  set4 = sliderwin2.addOptionalSlider(_INTL("Zoom Y:"), 5, 1000, 100)
  set5 = sliderwin2.addOptionalSlider(_INTL("Angle:"), 0, 359, 0)
  set6 = sliderwin2.addOptionalSlider(_INTL("Opacity:"), 0, 255, 255)
  set7 = sliderwin2.addOptionalSlider(_INTL("Blending:"), 0, 2, 0)
  set8 = sliderwin2.addOptionalTextSlider(_INTL("Flip:"), [_INTL("False"), _INTL("True")], 0)
  prio = [_INTL("Back"), _INTL("Front"), _INTL("Below focus"), _INTL("Above focus"), _INTL("Between sides"),
          _INTL("Behind Pokémon")]
  set9 = sliderwin2.addOptionalTextSlider(_INTL("Priority:"), prio, 1)
  foc = [_INTL("User"), _INTL("Target"), _INTL("User and target"), _INTL("Screen")]
  curfoc = [3, 1, 0, 2, 3][canvas.animation.position || 4]
  set10 = sliderwin2.addOptionalTextSlider(_INTL("Focus:"), foc, curfoc)
  flashbutton = sliderwin2.addButton(_INTL("Set Blending Color"))
  tonebutton = sliderwin2.addButton(_INTL("Set Color Tone"))
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  blendhash = {
    "blendcolorred" => 0,
    "blendredchange" => false,
    "blendcolorgreen" => 0,
    "blendgreenchange" => false,
    "blendcolorblue" => 0,
    "blendbluechange" => false,
    "blendcoloralpha" => 0,
    "blendalphachange" => false
  }
  tonehash = {
    "colortonered" => 0,
    "toneredchange" => false,
    "colortonegreen" => 0,
    "tonegreenchange" => false,
    "colortoneblue" => 0,
    "tonebluechange" => false,
    "colortonegray" => 0,
    "tonegraychange" => false,
  }
  loop do
    Graphics.update
    Input.update
    sliderwin1.update
    sliderwin2.update
    if sliderwin2.changed?(flashbutton)
      pbSetFlashBatch(blendhash)
    end
    if sliderwin2.changed?(tonebutton)
      pbSetToneBatch(tonehash)
    end
    if sliderwin2.changed?(okbutton) || Input.trigger?(Input::C)
      startframe = sliderwin1.value(s1set0) - 1
      endframe = sliderwin1.value(s1set1) - 1
      startcel = sliderwin1.value(s1set2)
      endcel = sliderwin1.value(s1set3)
      for i in startframe..endframe
        for j in startcel..endcel
          next if !canvas.animation[i][j]

          cel = canvas.animation[i][j]
          cel[AnimFrame::PATTERN] = sliderwin2.value(set0) if sliderwin2.value(set0)
          cel[AnimFrame::PATTERN] += sliderwin2.value(set11) if sliderwin2.value(set11)
          cel[AnimFrame::X] = sliderwin2.value(set1) if sliderwin2.value(set1)
          cel[AnimFrame::Y] = sliderwin2.value(set2) if sliderwin2.value(set2)
          cel[AnimFrame::ZOOMX] = sliderwin2.value(set3) if sliderwin2.value(set3)
          cel[AnimFrame::ZOOMY] = sliderwin2.value(set4) if sliderwin2.value(set4)
          cel[AnimFrame::ANGLE] = sliderwin2.value(set5) if sliderwin2.value(set5)
          cel[AnimFrame::OPACITY] = sliderwin2.value(set6) if sliderwin2.value(set6)
          cel[AnimFrame::BLENDTYPE] = sliderwin2.value(set7) if sliderwin2.value(set7)
          cel[AnimFrame::MIRROR] = sliderwin2.value(set8) if sliderwin2.value(set8)
          cel[AnimFrame::PRIORITY] = sliderwin2.value(set9) if sliderwin2.value(set9)
          cel[AnimFrame::FOCUS] = [2, 1, 3, 4][sliderwin2.value(set10)] if sliderwin2.value(set10)
          cel[AnimFrame::COLORRED] = blendhash["blendcolorred"] if blendhash["blendredchange"]
          cel[AnimFrame::COLORGREEN] = blendhash["blendcolorgreen"] if blendhash["blendgreenchange"]
          cel[AnimFrame::COLORBLUE] = blendhash["blendcolorblue"] if blendhash["blendbluechange"]
          cel[AnimFrame::COLORALPHA] = blendhash["blendcoloralpha"] if blendhash["blendalphachange"]
          cel[AnimFrame::TONERED] = tonehash["colortonered"] if tonehash["toneredchange"]
          cel[AnimFrame::TONEGREEN] = tonehash["colortonegreen"] if tonehash["tonegreenchange"]
          cel[AnimFrame::TONEBLUE] = tonehash["colortoneblue"] if tonehash["tonebluechange"]
          cel[AnimFrame::TONEGRAY] = tonehash["colortonegray"] if tonehash["tonegraychange"]
        end
      end
      canvas.invalidate
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin1.dispose
  sliderwin2.dispose
end

def pbEntireSlide(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 7)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.addSlider(_INTL("First Frame:"), 1, canvas.animation.length, 1)
  sliderwin2.addSlider(_INTL("Last Frame:"), 1, canvas.animation.length, canvas.animation.length)
  sliderwin2.addSlider(_INTL("X-Axis Movement"), -500, 500, 0)
  sliderwin2.addSlider(_INTL("Y-Axis Movement"), -500, 500, 0)
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  sliderwin2.opacity = 200
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      startvalue = sliderwin2.value(0) - 1
      endvalue = sliderwin2.value(1) - 1
      xoffset = sliderwin2.value(2)
      yoffset = sliderwin2.value(3)
      for i in startvalue..endvalue
        canvas.offsetFrame(i, xoffset, yoffset)
      end
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbHelpWindow
  helptext = "" +
             "To add a cel to the scene, click on the canvas.  The selected cel will have a black " +
             "frame.  After a cel is selected, you can modify its properties using the keyboard:\n" +
             "E/R - Rotate clockwise/anticlockwise\nP - Open properties screen;\nArrow keys - Move cel 8 pixels " +
             "(hold A - 2 pixels, hold Z - snap)\nQ/W : Zoom in/out\nL - Lock a cel (Prevents it " +
             "from being moved/deleted)\nDEL - Deletes the cel\nN - Renumber cel\nV/B - Move to previous/next " +
             "frame\nSpace - Play animation (Hold A for opp)\nAlso press TAB to switch to the next cel, or hold A " +
             "and press TAB to switch to the previous cel."
  cmdwin = Window_UnformattedTextPokemon.newWithSize("", 0, 0, 640, 512)
  cmdwin.opacity = 224
  cmdwin.z = 99999
  cmdwin.text = helptext
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    if Input.trigger?(Input::C)
      break
    end
    if Input.trigger?(Input::B)
      break
    end
  end
  cmdwin.dispose
end

################################################################################
# Pop-up menus for buttons in bottom menu
################################################################################
def pbSelectAnim(canvas, animwin)
  animfiles = []
  pbRgssChdir("./Graphics/Animations/") {
    animfiles.concat(Dir.glob("*.png"))
  }
  animfiles.sort!
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.opacity = 200
  cmdwin.height = 512
  bmpwin = BitmapDisplayWindow.new(320, 0, 320, 448)
  ctlwin = ControlWindow.new(320, 448, 320, 64)
  cmdwin.viewport = canvas.viewport
  bmpwin.viewport = canvas.viewport
  ctlwin.viewport = canvas.viewport
  ctlwin.addSlider(_INTL("Hue:"), 0, 359, 0)
  loop do
    bmpwin.bitmapname = cmdwin.commands[cmdwin.index]
    Graphics.update
    Input.update
    cmdwin.update
    bmpwin.update
    ctlwin.update
    if ctlwin.changed?(0)
      bmpwin.hue = ctlwin.value(0)
    end
    if Input.trigger?(Input::C) && animfiles.length > 0
      bitmap = AnimatedBitmap.new("Graphics/Animations/" + cmdwin.commands[cmdwin.index], ctlwin.value(0)).deanimate
      canvas.animation.graphic = cmdwin.commands[cmdwin.index]
      canvas.animation.hue = ctlwin.value(0)
      canvas.animbitmap = bitmap
      animwin.animbitmap = bitmap
      break
    elsif Input.trigger?(Input::B)
      break
    elsif Input.trigger?(Input::Y)
      pbSearchAnimSheet(canvas, animwin, animfiles)
      break
    end
  end
  bmpwin.dispose
  cmdwin.dispose
  ctlwin.dispose
  return
end

def pbSearchAnimSheet(canvas, animwin, animfiles)
  window = ControlWindow.new(320, 128, 320, 32 * 4)
  window.z = 99999
  window.addControl(TextField.new(_INTL("Search for:"), ""))
  okbutton = window.addButton(_INTL("OK"))
  cancelbutton = window.addButton(_INTL("Cancel"))
  window.opacity = 256
  breaking_off = false
  searchname = nil
  Input.text_input = true
  loop do
    Graphics.update
    Input.update
    window.update
    if window.changed?(okbutton) || Input.triggerex?(:RETURN)
      searchname = window.controls[0].text
      break
    end
    if window.changed?(cancelbutton) || Input.triggerex?(:ESCAPE)
      breaking_off = true
      break
    end
  end
  Input.text_input = false
  if breaking_off
    window.dispose
    return false
  end
  window.dispose
  newanimations = []
  searchnameupcase = searchname.upcase
  for i in 0...animfiles.length
    upcasednames = animfiles[i].upcase
    if upcasednames.include?(searchnameupcase)
      newanimations.push(animfiles[i])
    end
  end
  if newanimations.length == 0
    Kernel.pbMessage("no results!")
    return false
  end
  animfiles = newanimations
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.opacity = 252
  cmdwin.height = 512
  cmdwin.index = 0
  bmpwin = BitmapDisplayWindow.new(320, 0, 320, 448)
  ctlwin = ControlWindow.new(320, 448, 320, 64)
  cmdwin.viewport = canvas.viewport
  bmpwin.viewport = canvas.viewport
  ctlwin.viewport = canvas.viewport
  ctlwin.addSlider(_INTL("Hue:"), 0, 359, 0)
  loop do
    bmpwin.bitmapname = cmdwin.commands[cmdwin.index]
    Graphics.update
    Input.update
    cmdwin.update
    bmpwin.update
    ctlwin.update
    if ctlwin.changed?(0)
      bmpwin.hue = ctlwin.value(0)
    end
    if Input.trigger?(Input::C) && animfiles.length > 0
      bitmap = AnimatedBitmap.new("Graphics/Animations/" + cmdwin.commands[cmdwin.index], ctlwin.value(0)).deanimate
      canvas.animation.graphic = cmdwin.commands[cmdwin.index]
      canvas.animation.hue = ctlwin.value(0)
      canvas.animbitmap = bitmap
      animwin.animbitmap = bitmap
      break
    elsif Input.trigger?(Input::B)
      break
    end
  end
  bmpwin.dispose
  cmdwin.dispose
  ctlwin.dispose
  return
end

def pbChangeMaximum(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 32 * 4)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.addSlider(_INTL("Frames:"), 1, 1000, canvas.animation.length)
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  sliderwin2.opacity = 200
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(okbutton)
      canvas.animation.resize(sliderwin2.value(0))
      break
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  sliderwin2.dispose
  return
end

def pbAnimName(animation, cmdwin)
  Input.text_input = true
  window = ControlWindow.new(320, 128, 320, 32 * 4)
  window.z = 99999
  window.addControl(TextField.new(_INTL("New Name:"), animation.name))
  okbutton = window.addButton(_INTL("OK"))
  cancelbutton = window.addButton(_INTL("Cancel"))
  window.opacity = 224
  loop do
    Graphics.update
    Input.update
    window.update
    if window.changed?(okbutton) || Input.triggerex?(0x0D)
      cmdwin.commands[cmdwin.index] = _INTL("{1} {2}", cmdwin.index, window.controls[0].text)
      animation.name = window.controls[0].text
      break
    end
    if window.changed?(cancelbutton) || Input.triggerex?(0x1B)
      break
    end
  end
  window.dispose
  Input.text_input = false
  return
end

def pbAnimList(animations, canvas, animwin)
  commands = []
  for i in 0...animations.length
    animations[i] = PBAnimation.new if !animations[i]
    commands[commands.length] = _INTL("{1} {2}", i, animations[i].name)
  end
  cmdwin = pbListWindow(commands, 320)
  cmdwin.height = 416
  cmdwin.opacity = 224
  cmdwin.index = animations.selected
  cmdwin.viewport = canvas.viewport
  helpwindow = Window_UnformattedTextPokemon.newWithSize(
    _INTL("C: Load/rename an animation\nS: Search\nEsc: Cancel"),
    320, 0, 320, 128, canvas.viewport
  )
  maxsizewindow = ControlWindow.new(0, 416, 320, 32 * 3)
  maxsizewindow.addSlider(_INTL("Total Animations:"), 1, 2000, animations.length)
  maxsizewindow.addButton(_INTL("Resize Animation List"))
  maxsizewindow.opacity = 224
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    helpwindow.update
    if maxsizewindow.changed?(1)
      newsize = maxsizewindow.value(0)
      animations.resize(newsize)
      commands.clear
      for i in 0...animations.length
        commands[commands.length] = _INTL("{1} {2}", i, animations[i].name)
      end
      cmdwin.commands = commands
      cmdwin.index = animations.selected
      next
    end
    if Input.trigger?(Input::C) && animations.length > 0
      cmd2 = Kernel.pbShowCommands(
        helpwindow,
        [
          _INTL("Load Animation"),
          _INTL("Rename"),
          _INTL("Delete")
        ],
        -1
      )
      if cmd2 == 0 # Load Animation
        canvas.loadAnimation(animations.reload(cmdwin.index))
        animwin.animbitmap = canvas.animbitmap
        break
      elsif cmd2 == 1 # Rename
        pbAnimName(animations[cmdwin.index], cmdwin)
        cmdwin.refresh
      elsif cmd2 == 2 # Delete
        if Kernel.pbConfirmMessage(_INTL("Are you sure you want to delete this animation?"))
          animations[cmdwin.index] = PBAnimation.new
          cmdwin.commands[cmdwin.index] = _INTL("{1} {2}", cmdwin.index, animations[cmdwin.index].name)
          cmdwin.refresh
        end
      end
    end
    if Input.trigger?(Input::Y)
      pbSearchAnimations(animations, cmdwin, canvas, animwin)
      break
    end
    if Input.trigger?(Input::B)
      break
    end
  end
  helpwindow.dispose
  maxsizewindow.dispose
  cmdwin.dispose
end

def pbSearchAnimations(animations, cmdwin, canvas, animwin)
  window = ControlWindow.new(320, 128, 320, 32 * 4)
  window.z = 99999
  window.addControl(TextField.new(_INTL("Search for:"), ""))
  okbutton = window.addButton(_INTL("OK"))
  cancelbutton = window.addButton(_INTL("Cancel"))
  window.opacity = 224
  breaking_off = false
  searchname = nil
  Input.text_input = true
  loop do
    Graphics.update
    Input.update
    window.update
    if window.changed?(okbutton) || Input.triggerex?(:RETURN)
      searchname = window.controls[0].text
      break
    end
    if window.changed?(cancelbutton) || Input.triggerex?(:ESCAPE)
      breaking_off = true
      break
    end
  end
  Input.text_input = false
  if breaking_off
    window.dispose
    return false
  end
  window.dispose
  hassearchterm = []
  newanimations = []
  searchnameupcase = searchname.upcase
  for i in 0...animations.length
    upcasednames = animations[i].name.upcase
    if upcasednames.include?(searchnameupcase)
      newanimations.push(animations[i])
      hassearchterm.push([i, animations[i]])
    end
  end
  if hassearchterm.length == 0
    Kernel.pbMessage("no results!")
    return
  end
  commands = []
  for i in 0...hassearchterm.length
    hassearchterm[i][1] = PBAnimation.new if !hassearchterm[i][1]
    commands[commands.length] = _INTL("{1} {2}", hassearchterm[i][0], hassearchterm[i][1].name)
  end
  cmdwin = pbListWindow(commands, 320)
  cmdwin.height = 416
  cmdwin.opacity = 224
  cmdwin.index = 0
  cmdwin.viewport = canvas.viewport
  helpwindow = Window_UnformattedTextPokemon.newWithSize(
    _INTL("C: Load/rename an animation\nEsc: Cancel"),
    320, 0, 320, 128, canvas.viewport
  )
  maxsizewindow = ControlWindow.new(0, 416, 320, 32 * 3)
  maxsizewindow.addSlider(_INTL("Total Animations:"), 1, 2000, newanimations.length)
  maxsizewindow.addButton(_INTL("Resize Animation List"))
  maxsizewindow.opacity = 224
  maxsizewindow.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    maxsizewindow.update
    helpwindow.update
    if Input.trigger?(Input::C) && animations.length > 0
      cmd2 = Kernel.pbShowCommands(
        helpwindow,
        [
          _INTL("Load Animation"),
          _INTL("Rename"),
          _INTL("Delete")
        ],
        -1
      )
      if cmd2 == 0 # Load Animation
        canvas.loadAnimation(animations.reload(hassearchterm[cmdwin.index][0]))
        animwin.animbitmap = canvas.animbitmap

        break
      elsif cmd2 == 1 # Rename
        pbAnimName(hassearchterm[cmdwin.index][1], cmdwin)
        cmdwin.refresh
      elsif cmd2 == 2 # Delete
        if Kernel.pbConfirmMessage(_INTL("Are you sure you want to delete this animation?"))
          hassearchterm[cmdwin.index][1] = PBAnimation.new
          cmdwin.commands[cmdwin.index] = _INTL("{1} {2}", cmdwin.index, hassearchterm[cmdwin.index][1].name)
          cmdwin.refresh
        end
      end
    end
    if Input.trigger?(Input::B)
      break
    end
  end
  helpwindow.dispose
  maxsizewindow.dispose
  cmdwin.dispose
  window.dispose
  return
end

################################################################################
# Importing and exporting
################################################################################
def pbRgssChdir(dir)
  RTP.eachPathFor(dir) { |path| Dir.chdir(path) { yield } }
end

def tryLoadData(file)
  begin
    return load_data(file)
  rescue
    print("error with loading animations. tell cass or someone.")
    return nil
  end
end

def dumpBase64Anim(s)
  return [Zlib::Deflate.deflate(Marshal.dump(s))].pack("m").gsub(/\n/, "\r\n")
end

def loadBase64Anim(s)
  return Marshal.restore(Zlib::Inflate.inflate(s.unpack("m")[0]))
end

def pbExportAnim(animations)
  Input.text_input = true
  filename = Kernel.pbMessageFreeText(_INTL("Enter a filename."), "", false, 32)
  if filename != ""
    begin
      filename += ".anm"
      File.open(filename, "wb") { |f|
        f.write(dumpBase64Anim(animations[animations.selected]))
      }
      failed = false
    rescue
      Kernel.pbMessage(_INTL("Couldn't save the animation to {1}.", filename))
      failed = true
    end
    if !failed
      Kernel.pbMessage(_INTL("Animation was saved to {1} in the game folder.", filename))
    end
  end
  Input.text_input = false
end

def testPrint
  $stdin.print("hi")
  $stdin.print(" there")
end

def pbImportAnim(animations, canvas, animwin)
  animfiles = []
  pbRgssChdir(".") {
    animfiles.concat(Dir.glob("*.anm"))
  }
  if animfiles.length > 1
    if Kernel.pbConfirmMessage("Multiple .anm files detected. Import all of them?")
      for file in animfiles
        $stdout.print("Importing animation #{file}...")
        begin
          textdata = loadBase64Anim(IO.read(file))
          throw "Bad data" if !textdata.is_a?(PBAnimation)
          textdata.id = -1 # this is not an RPG Maker XP animation
          pbConvertAnimToNewFormat(textdata)
          animations[animations.selected] = textdata
        rescue
          $stdout.print("failed\n")
          puts "Animation #{file} is invalid or could not be loaded"
          next
        end
        graphic = animations[animations.selected].graphic
        graphic = "Graphics/Animations/#{graphic}"
        if graphic && graphic != "" && !FileTest.image_exist?(graphic)
          Kernel.pbMessage(_INTL("The animation file {1} was not found.  The animation will load anyway.", graphic))
        end
        animations.save(animations.selected)
        canvas.loadAnimation(animations.reload(animations.selected))
        animwin.animbitmap = canvas.animbitmap
        $stdout.print("done\n")
      end
      print "Finished importing"
      return
    end
  end
  cmdwin = pbListWindow(animfiles, 320)
  cmdwin.opacity = 200
  cmdwin.height = 480
  cmdwin.viewport = canvas.viewport
  loop do
    Graphics.update
    Input.update
    cmdwin.update
    if Input.trigger?(Input::C) && animfiles.length > 0
      begin
        textdata = loadBase64Anim(IO.read(animfiles[cmdwin.index]))
        throw "Bad data" if !textdata.is_a?(PBAnimation)
        textdata.id = -1 # this is not an RPG Maker XP animation
        pbConvertAnimToNewFormat(textdata)
        animations[animations.selected] = textdata
      rescue
        Kernel.pbMessage(_INTL("The animation is invalid or could not be loaded."))
        next
      end
      graphic = animations[animations.selected].graphic
      graphic = "Graphics/Animations/#{graphic}"
      if graphic && graphic != "" && !FileTest.image_exist?(graphic)
        Kernel.pbMessage(_INTL("The animation file {1} was not found.  The animation will load anyway.", graphic))
      end
      animations.save(animations.selected)
      canvas.loadAnimation(animations.reload(animations.selected))
      animwin.animbitmap = canvas.animbitmap
      break
    end
    if Input.trigger?(Input::B)
      break
    end
  end
  cmdwin.dispose
  return
end

################################################################################
# Paths and interpolation
################################################################################
class ControlPointSprite < SpriteWrapper
  attr_accessor :dragging

  def initialize(red, viewport = nil)
    super(viewport)
    self.bitmap = Bitmap.new(6, 6)
    self.bitmap.fill_rect(0, 0, 6, 1, Color.new(0, 0, 0))
    self.bitmap.fill_rect(0, 0, 1, 6, Color.new(0, 0, 0))
    self.bitmap.fill_rect(0, 5, 6, 1, Color.new(0, 0, 0))
    self.bitmap.fill_rect(5, 0, 1, 6, Color.new(0, 0, 0))
    color = (red) ? Color.new(255, 0, 0) : Color.new(0, 0, 0)
    self.bitmap.fill_rect(2, 2, 2, 2, color)
    self.x = -6
    self.y = -6
    self.visible = false
    @dragging = false
  end

  def mouseover
    if Input.repeatcount(0x01) == 0 || !@dragging
      @dragging = false
      return
    end
    mouse = Mouse::getMousePos(true)
    self.x = [[mouse[0], 0].max, 512].min
    self.y = [[mouse[1], 0].max, 384].min
  end

  def hittest?
    return true if !self.visible

    mouse = Mouse::getMousePos(true)
    return true if mouse[0] >= self.x && mouse[0] < self.x + 6 &&
                   mouse[1] >= self.y && mouse[1] < self.y + 6
  end

  def inspect
    return "[#{self.x},#{self.y}]"
  end

  def dispose
    self.bitmap.dispose
    super
  end
end

class PointSprite < SpriteWrapper
  def initialize(x, y, viewport = nil)
    super(viewport)
    self.bitmap = Bitmap.new(2, 2)
    self.bitmap.fill_rect(0, 0, 2, 2, Color.new(0, 0, 0))
    self.x = x
    self.y = y
  end

  def dispose
    self.bitmap.dispose
    super
  end
end

class PointPath
  include Enumerable

  def initialize
    @points = []
    @distances = []
    @totaldist = 0
  end

  def [](x)
    return @points[x].clone
  end

  def each
    @points.each { |o| yield o.clone }
  end

  def size
    return @points.size
  end

  def length
    return @points.length
  end

  def totalDistance
    return @totaldist
  end

  def inspect
    p = []
    for point in @points
      p.push([point[0].to_i, point[1].to_i])
    end
    return p.inspect
  end

  def isEndPoint?(x, y)
    return false if @points.length == 0

    index = @points.length - 1
    return @points[index][0] == x &&
           @points[index][1] == y
  end

  def addPoint(x, y)
    @points.push([x, y])
    if @points.length > 1
      len = @points.length
      dx = @points[len - 2][0] - @points[len - 1][0]
      dy = @points[len - 2][1] - @points[len - 1][1]
      dist = Math.sqrt(dx * dx + dy * dy)
      @distances.push(dist)
      @totaldist += dist
    end
  end

  def clear
    @points.clear
    @distances.clear
    @totaldist = 0
  end

  def smoothPointPath(frames, roundValues = false)
    if frames < 0
      raise ArgumentError.new("frames out of range: #{frames}")
    end

    ret = PointPath.new
    if @points.length == 0
      return ret
    end

    step = 1.0 / frames
    t = 0.0;
    for i in 0..frames + 1
      point = pointOnPath(t)
      if roundValues
        ret.addPoint(point[0].round, point[1].round)
      else
        ret.addPoint(point[0], point[1])
      end
      t += step
      t = [1.0, t].min
    end
    return ret
  end

  def pointOnPath(t)
    if t < 0 || t > 1
      raise ArgumentError.new("t out of range for pointOnPath: #{t}")
    end
    return nil if @points.length == 0

    ret = @points[@points.length - 1].clone
    if @points.length == 1
      return ret
    end

    curdist = 0
    distForT = @totaldist * t
    i = 0
    for dist in @distances
      curdist += dist
      if dist > 0.0
        if curdist >= distForT
          distT = 1.0 - ((curdist - distForT) / dist)
          dx = @points[i + 1][0] - @points[i][0]
          dy = @points[i + 1][1] - @points[i][1]
          ret = [@points[i][0] + dx * distT,
                 @points[i][1] + dy * distT]
          break
        end
      end
      i += 1
    end
    return ret
  end
end

def getCubicPoint(src, t)
  x0 = src[0].x
  y0 = src[0].y
  cx0 = src[1].x
  cy0 = src[1].y
  cx1 = src[2].x
  cy1 = src[2].y
  x1 = src[3].x
  y1 = src[3].y
  x1 = cx1 + (x1 - cx1) * t
  y1 = cy1 + (y1 - cy1) * t
  x0 = x0 + (cx0 - x0) * t
  y0 = y0 + (cy0 - y0) * t
  cx0 = cx0 + (cx1 - cx0) * t
  cy0 = cy0 + (cy1 - cy0) * t
  cx1 = cx0 + (x1 - cx0) * t
  cy1 = cy0 + (y1 - cy0) * t
  cx0 = x0 + (cx0 - x0) * t
  cy0 = y0 + (cy0 - y0) * t
  cx = cx0 + (cx1 - cx0) * t
  cy = cy0 + (cy1 - cy0) * t
  return [cx, cy]
end

def catmullRom(p1, p2, p3, p4, t)
  # p1=prevPoint, p2=startPoint, p3=endPoint, p4=nextPoint, t is from 0 through 1
  t2 = t * t
  t3 = t2 * t
  return 0.5 * (2 * p2 + t * (p3 - p1) +
         t2 * (2 * p1 - 5 * p2 + 4 * p3 - p4) +
         t3 * (p4 - 3 * p3 + 3 * p2 - p1))
end

def getCatmullRomPoint(src, t)
  x = 0, y = 0
  t *= 3.0
  if t < 1.0
    x = catmullRom(src[0].x, src[0].x, src[1].x, src[2].x, t)
    y = catmullRom(src[0].y, src[0].y, src[1].y, src[2].y, t)
  elsif t < 2.0
    t -= 1.0
    x = catmullRom(src[0].x, src[1].x, src[2].x, src[3].x, t)
    y = catmullRom(src[0].y, src[1].y, src[2].y, src[3].y, t)
  else
    t -= 2.0
    x = catmullRom(src[1].x, src[2].x, src[3].x, src[3].x, t)
    y = catmullRom(src[1].y, src[2].y, src[3].y, src[3].y, t)
  end
  return [x, y]
end

def getCurvePoint(src, t)
  return getCatmullRomPoint(src, t)
end

def curveToPointPath(curve, numpoints)
  if numpoints < 2
    return nil
  end

  path = PointPath.new
  step = 1.0 / (numpoints - 1)
  t = 0.0
  for i in 0...numpoints
    point = getCurvePoint(curve, t)
    path.addPoint(point[0], point[1])
    t += step
  end
  return path
end

def pbDefinePath(canvas)
  sliderwin2 = ControlWindow.new(0, 0, 320, 320)
  sliderwin2.viewport = canvas.viewport
  sliderwin2.addSlider(_INTL("Number of frames:"), 2, 500, 20)
  sliderwin2.opacity = 200
  defcurvebutton = sliderwin2.addButton(_INTL("Define Curve"))
  defpathbutton = sliderwin2.addButton(_INTL("Define Path"))
  okbutton = sliderwin2.addButton(_INTL("OK"))
  cancelbutton = sliderwin2.addButton(_INTL("Cancel"))
  points = []
  path = nil
  loop do
    Graphics.update
    Input.update
    sliderwin2.update
    if sliderwin2.changed?(0)
      if path
        path = path.smoothPointPath(sliderwin2.value(0), false)
        i = 0
        for point in path
          if i < points.length
            points[i].x = point[0]
            points[i].y = point[1]
          else
            points.push(PointSprite.new(point[0], point[1], canvas.viewport))
          end
          i += 1
        end
        for j in i...points.length
          points[j].dispose
          points[j] = nil
        end
        points.compact!
        #       File.open("pointpath.txt","wb"){|f| f.write(path.inspect) }
      end
    end
    if sliderwin2.changed?(okbutton) && path
      #     File.open("pointpath.txt","wb"){|f|
      #        f.write(path.inspect)
      #     }
      neededsize = canvas.currentframe + sliderwin2.value(0)
      if neededsize > canvas.animation.length
        canvas.animation.resize(neededsize)
      end
      thiscel = canvas.currentCel
      celnumber = canvas.currentcel
      for i in canvas.currentframe...neededsize
        cel = canvas.animation[i][celnumber]
        if !canvas.animation[i][celnumber]
          cel = pbCreateCel(0, 0, thiscel[AnimFrame::PATTERN], canvas.animation.position)
          canvas.animation[i][celnumber] = cel
        end
        if @path # In case you try to click ok without defining anything
          cel[AnimFrame::X] = (path[i - canvas.currentframe][0]) - 64
          cel[AnimFrame::Y] = (path[i - canvas.currentframe][1]) - 64
        end
      end
      startframe = sliderwin2.value(0)
      break
    end
    if sliderwin2.changed?(defcurvebutton)
      for point in points
        point.dispose
      end
      points.clear
      30.times do
        point = PointSprite.new(0, 0, canvas.viewport)
        point.visible = false
        points.push(point)
      end
      curve = [
        ControlPointSprite.new(true, canvas.viewport),
        ControlPointSprite.new(false, canvas.viewport),
        ControlPointSprite.new(false, canvas.viewport),
        ControlPointSprite.new(true, canvas.viewport)
      ]
      showline = false
      sliderwin2.visible = false
      # This window displays the mouse's current position
      window = Window_UnformattedTextPokemon.new("")
      window.x = 0
      window.y = 320 - 64
      window.width = 128
      window.height = 64
      window.viewport = canvas.viewport
      loop do
        Graphics.update
        Input.update
        redrawline = false
        if Input.trigger?(Input::B)
          break
        end

        if Input.triggerex?(Input::LeftMouseKey)
          for j in 0...4
            next if !curve[j].hittest?

            if j == 1 || j == 2
              next if !curve[0].visible || !curve[3].visible
            end
            curve[j].visible = true
            for k in 0...4
              curve[k].dragging = (k == j)
            end
            break
          end
        end
        for j in 0...4
          curve[j].mouseover
        end
        mouse = Mouse::getMousePos(true)
        newtext = sprintf("(%d,%d)", mouse[0], mouse[1])
        if window.text != newtext
          window.text = newtext
        end
        if curve[0].visible && curve[3].visible &&
           !curve[0].dragging && !curve[3].dragging
          for point in points
            point.visible = true
          end
          if !showline
            curve[1].visible = true
            curve[2].visible = true
            curve[1].x = curve[0].x + 0.3333 * (curve[3].x - curve[0].x)
            curve[1].y = curve[0].y + 0.3333 * (curve[3].y - curve[0].y)
            curve[2].x = curve[0].x + 0.6666 * (curve[3].x - curve[0].x)
            curve[2].y = curve[0].y + 0.6666 * (curve[3].y - curve[0].y)
          end
          showline = true
        end
        if showline
          step = 1.0 / (points.length - 1)
          t = 0.0
          for i in 0...points.length
            point = getCurvePoint(curve, t)
            points[i].x = point[0]
            points[i].y = point[1]
            t += step
          end
        end
      end
      window.dispose
      # dispose temporary path
      for point in points
        point.dispose
      end
      points.clear
      if showline
        path = curveToPointPath(curve, sliderwin2.value(0))
        #       File.open("pointpath.txt","wb"){|f| f.write(path.inspect) }
        for point in path
          points.push(PointSprite.new(point[0], point[1], canvas.viewport))
        end
      end
      for point in curve
        point.dispose
      end
      sliderwin2.visible = true
      next
    elsif sliderwin2.changed?(defpathbutton)
      canceled = false
      pointpath = PointPath.new
      for point in points
        point.dispose
      end
      points.clear
      window = Window_UnformattedTextPokemon.newWithSize(
        "",
        0, 320 - 64, 128, 64, canvas.viewport
      )
      sliderwin2.visible = false
      loop do
        Graphics.update
        Input.update
        if Input.triggerex?(0x1b)
          canceled = true
          break
        end
        if Input.triggerex?(Input::LeftMouseKey)
          break
        end

        mousepos = Mouse::getMousePos(true)
        window.text = sprintf("(%d,%d)", mousepos[0], mousepos[1])
      end
      while !canceled
        mousepos = Mouse::getMousePos(true)
        if !pointpath.isEndPoint?(mousepos[0], mousepos[1])
          pointpath.addPoint(mousepos[0], mousepos[1])
          points.push(PointSprite.new(mousepos[0], mousepos[1], canvas.viewport))
        end
        window.text = sprintf("(%d,%d)", mousepos[0], mousepos[1])
        Graphics.update
        Input.update
        if Input.triggerex?(0x1b) || Input.repeatcount(0x01) == 0
          break
        end
      end
      window.dispose
      # dispose temporary path
      for point in points
        point.dispose
      end
      points.clear
      # generate smooth path from temporary path
      path = pointpath.smoothPointPath(sliderwin2.value(0), true)
      # redraw path from smooth path
      for point in path
        points.push(PointSprite.new(point[0], point[1], canvas.viewport))
      end
      #     File.open("pointpath.txt","wb"){|f| f.write(path.inspect) }
      sliderwin2.visible = true
      next
    end
    if sliderwin2.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  # dispose all points
  for point in points
    point.dispose
  end
  points.clear
  sliderwin2.dispose
  return
end

################################################################################
# Window classes
################################################################################
class BitmapDisplayWindow < SpriteWindow_Base
  attr_reader :bitmapname
  attr_reader :hue

  def initialize(x, y, width, height)
    super(x, y, width, height)
    @bitmapname = ""
    @hue = 0
    self.contents = Bitmap.new(width - 32, height - 32)
  end

  def bitmapname=(value)
    if @bitmapname != value
      @bitmapname = value
      refresh
    end
  end

  def hue=(value)
    if @hue != value
      @hue = value
      refresh
    end
  end

  def refresh
    self.contents.clear
    bmap = AnimatedBitmap.new("Graphics/Animations/" + @bitmapname, @hue).deanimate
    return if !bmap

    ww = bmap.width
    wh = bmap.height
    sx = self.contents.width * 1.0 / ww
    sy = self.contents.height * 1.0 / wh
    if sx > sy
      ww = sy * ww
      wh = self.contents.height
    else
      wh = sx * wh
      ww = self.contents.width
    end
    dest = Rect.new(
      (self.contents.width - ww) / 2,
      (self.contents.height - wh) / 2,
      ww, wh
    )
    src = Rect.new(0, 0, bmap.width, bmap.height)
    self.contents.stretch_blt(dest, bmap, src)
    bmap.dispose
  end
end

class AnimationNameWindow
  def initialize(canvas, x, y, width, height, viewport = nil)
    @canvas = canvas
    @oldname = nil
    @window = Window_UnformattedTextPokemon.newWithSize(
      _INTL("Name: {1}", @canvas.animation.name), x, y, width, height, viewport
    )
  end

  def viewport=(value); @window.viewport = value; end

  def update
    newtext = _INTL("Name: {1}", @canvas.animation.name)
    if @oldname != newtext
      @window.text = newtext
      @oldname = newtext
    end
    @window.update
  end

  def refresh; @window.refresh; end
  def dispose; @window.dispose; end
  def disposed; @window.disposed?; end
end

################################################################################
# Main
################################################################################
def animationEditorMain(animation)
  viewport = Viewport.new(0, 0, 825, 672)
  Graphics.resize_screen(825, 672)
  viewport.z = 99999
  # Canvas
  canvas = AnimationCanvas.new(animation.reload(animation.selected), viewport)
  # Right hand menu
  sidewin = ControlWindow.new(640, 0, 185, 512)
  sidewin.addButton(_INTL("SE and BG..."))
  sidewin.addButton(_INTL("Cel Focus..."))
  sidewin.addButton(_INTL("Paste Last"))
  sidewin.addButton(_INTL("Lock Creation"))
  sidewin.addSpace
  sidewin.addButton(_INTL("Cel Functions"))
  sidewin.addSpace
  sidewin.addButton(_INTL("Frame Functions"))
  sidewin.addSpace
  sidewin.addButton(_INTL("Play Animation"))
  sidewin.addButton(_INTL("Play Opp Anim"))
  sidewin.addButton(_INTL("Import Anim..."))
  sidewin.addButton(_INTL("Export Anim..."))
  sidewin.addButton(_INTL("Targets"))
  sidewin.addButton(_INTL("Settings"))
  sidewin.viewport = canvas.viewport
  # Bottom left menu
  sliderwin = ControlWindow.new(0, 502, 240, 160)
  sliderwin.addControl(FrameCountSlider.new(canvas))
  sliderwin.addControl(FrameCountButton.new(canvas))
  sliderwin.addButton(_INTL("Set Animation Sheet"))
  sliderwin.addButton(_INTL("List of Animations"))
  sliderwin.viewport = canvas.viewport
  # Animation sheet window
  animwin = CanvasAnimationWindow.new(canvas, 240, 384 + 128, 512, 96, canvas.viewport)
  # Name window
  bottomwindow = AnimationNameWindow.new(canvas, 240, 384 + 128 + 96, 512, 64, canvas.viewport)
  loop do
    Graphics.update
    Input.update
    sliderwin.update
    canvas.update
    sidewin.update
    animwin.update
    bottomwindow.update
    if animwin.changed?
      canvas.pattern = animwin.selected
    end
    if Input.trigger?(Input::B)
      if Kernel.pbConfirmMessage(_INTL("Save changes?"))
        saveAnimations(animation)
      end
      if Kernel.pbConfirmMessage(_INTL("Exit from the editor?"))
        Graphics.resize_screen(DEFAULTSCREENWIDTH + 2 * $ResizeOffsetX, DEFAULTSCREENHEIGHT + 2 * $ResizeOffsetY)
        break
      end
    end
    if Input.trigger?(Input::Y)
      pbHelpWindow
      next
    elsif Input.triggerex?(Input::RightMouseKey) && sliderwin.hittest?(0) # Right mouse button
      commands = [
        _INTL("Copy Frame"),
        _INTL("Paste Frame"),
        _INTL("Clear Frame"),
        _INTL("Insert Frame"),
        _INTL("Delete Frame")
      ]
      hit = pbTrackPopupMenu(commands)
      case hit
        when 0 # Copy
          if canvas.currentframe >= 0
            Clipboard.setData(canvas.animation[canvas.currentframe], "PBAnimFrame")
          end
        when 1 # Paste
          if canvas.currentframe >= 0
            canvas.pasteFrame(canvas.currentframe)
          end
        when 2 # Clear Frame
          canvas.clearFrame(canvas.currentframe)
        when 3 # Insert Frame
          canvas.insertFrame(canvas.currentframe)
          sliderwin.invalidate
        when 4 # Delete Frame
          canvas.deleteFrame(canvas.currentframe)
          sliderwin.controls[0].curvalue = canvas.currentframe + 1
          sliderwin.invalidate
      end
      next
    elsif Input.triggerex?(0x56) || Input.repeatex?(0x56) # V
      canvas.currentframe -= 1 if canvas.currentframe > 0
      sliderwin.controls[0].curvalue = canvas.currentframe + 1
      # This is the code for define path but it's broken so I'm yeeting it until I fix it
      # if canvas.currentCel
      #  pbDefinePath(canvas)
      #  sliderwin.invalidate
      # end
      next
    elsif Input.triggerex?(0x42) || Input.repeatex?(0x42) # B
      canvas.currentframe += 1 if canvas.currentframe < canvas.animation.length - 1
      sliderwin.controls[0].curvalue = canvas.currentframe + 1
      next
    elsif Input.triggerex?(0x20) # Space
      if Input.press?(Input::X)
        canvas.play(true)
      else
        canvas.play
      end
      next
    elsif Input.triggerex?(Input::RightMouseKey) # Right mouse button
      mousepos = Mouse::getMousePos
      mousepos = [0, 0] if !mousepos
      commands = [
        _INTL("Properties..."),
        _INTL("Cut"),
        _INTL("Copy"),
        _INTL("Paste"),
        _INTL("Delete"),
        _INTL("Renumber..."),
        _INTL("Select From List")
        # _INTL("Define Path...")
      ]
      hit = pbTrackPopupMenu(commands)
      case hit
        when 0 # Properties
          if canvas.currentCel
            pbCellProperties(canvas)
            canvas.invalidateCel(canvas.currentcel)
          end
        when 1 # Cut
          if canvas.currentCel
            Clipboard.setData(canvas.currentCel, "PBAnimCel")
            canvas.deleteCel(canvas.currentcel)
          end
        when 2 # Copy
          if canvas.currentCel
            Clipboard.setData(canvas.currentCel, "PBAnimCel")
          end
        when 3 # Paste
          canvas.pasteCel(mousepos[0], mousepos[1])
        when 4 # Delete
          canvas.deleteCel(canvas.currentcel)
        when 5 # Renumber
          if canvas.currentcel && canvas.currentcel >= 2
            cel1 = canvas.currentcel
            cel2 = pbChooseNum(cel1)
            if cel2 >= 2 && cel1 != cel2
              canvas.swapCels(cel1, cel2)
            end
          end
        when 6 # Select From List
          canvas.listSelect(canvas, mousepos[0], mousepos[1])
        # when 7 # Define Path
        # if canvas.currentCel
        # pbDefinePath(canvas)
        # sliderwin.invalidate
        # end
      end
      next
    end
    if sliderwin.changed?(0) # Current frame changed
      canvas.currentframe = sliderwin.value(0) - 1
    end
    if sliderwin.changed?(1) # Change frame count
      pbChangeMaximum(canvas)
      sliderwin.refresh
    end
    if sliderwin.changed?(2) # Set Animation Sheet
      pbSelectAnim(canvas, animwin)
      animwin.refresh
      sliderwin.refresh
    end
    if sliderwin.changed?(3) # List of Animations
      pbAnimList(animation, canvas, animwin)
      sliderwin.controls[0].curvalue = canvas.currentframe + 1
      bottomwindow.refresh
      animwin.refresh
      sliderwin.refresh
      sidewin.refresh
    end
    if sidewin.changed?(0)
      pbTimingList(canvas)
    end
    if sidewin.changed?(1)
      positions = [_INTL("User"), _INTL("Target"), _INTL("User and target"), _INTL("Screen")]
      indexes = [2, 1, 3, 4] # Keeping backwards compatibility
      for i in 0...positions.length
        selected = "[  ]"
        if animation[animation.selected].position == indexes[i]
          selected = "[x]"
        end
        positions[i] = sprintf("%s %s", selected, positions[i])
      end
      pos = Kernel.pbShowCommands(nil, positions, -1)
      if pos >= 0
        animation[animation.selected].position = indexes[pos]
        canvas.update
      end
    end
    if sidewin.changed?(2)
      canvas.pasteLast
    end
    if sidewin.changed?(3)
      if canvas.createLock == false
        canvas.createLock = true
        sidewin.controls[3].text = "Unlock Creation"
        sidewin.refresh
      else
        canvas.createLock = false
        sidewin.controls[3].text = "Lock Creation"
        sidewin.refresh
      end
    end
    if sidewin.changed?(5)
      pbCelMenu(canvas)
    end
    if sidewin.changed?(7)
      pbFrameMenu(canvas)
    end
    if sidewin.changed?(9)
      canvas.play
    end
    if sidewin.changed?(10)
      canvas.play(true)
    end
    if sidewin.changed?(11)
      pbImportAnim(animation, canvas, animwin)
      sliderwin.controls[0].curvalue = canvas.currentframe + 1
      bottomwindow.refresh
      animwin.refresh
      sliderwin.refresh
    end
    if sidewin.changed?(12)
      pbExportAnim(animation)
      bottomwindow.refresh
      animwin.refresh
      sliderwin.refresh
    end
    if sidewin.changed?(13)
      positions = [_INTL("Single Target"), _INTL("Both Opponents"), _INTL("All Targets"), _INTL("Hide Partners")]
      for i in 0...positions.length
        selected = "[ ]"
        if animation[animation.selected].targets == i
          selected = "[x]"
        end
        positions[i] = sprintf("%s %s", selected, positions[i])
      end
      pos = Kernel.pbShowCommands(nil, positions, -1)
      if pos >= 0
        animation[animation.selected].targets = pos
        canvas.update
      end
    end
    if sidewin.changed?(14)
      pbGlobalAnimSettings(canvas, animation)
    end
  end
  canvas.dispose
  animwin.dispose
  sliderwin.dispose
  sidewin.dispose
  bottomwindow.dispose
  viewport.dispose
  GC.start
end

def pbAnimationEditor
  pbBGMStop()
  animations = loadAnimations
  if !animations || !animations[0]
    animations = PBAnimations.new
    animations[0].graphic = ""
  end
  animationEditorMain(animations)
  $game_map.autoplay if $game_map
end

################################################################################
# Some extra menus
################################################################################

def pbCelMenu(canvas)
  celwin = ControlWindow.new(420, 0, 220, 512) # Can change if things overlap
  celwin.viewport = canvas.viewport
  celwin.addButton(_INTL("Copy Cels"))
  celwin.addSpace
  celwin.addButton(_INTL("Tweening"))
  celwin.addSpace
  celwin.addButton(_INTL("Cel Batch"))
  celwin.addSpace
  celwin.addButton(_INTL("Flip Cel Rotation"))
  celwin.addSpace
  celwin.addButton(_INTL("Flip Around Point"))
  celwin.addSpace
  celwin.addButton(_INTL("Renumber Cel Batch"))
  loop do
    Graphics.update
    Input.update
    celwin.update
    if (Input.triggerex?(Input::LeftMouseKey) && Mouse::getMousePos[0] < 440) || Input.trigger?(Input::B)
      celwin.dispose
      break
    end
    if celwin.changed?(0)
      celwin.dispose
      pbCopyCels(canvas)
      break
    end
    if celwin.changed?(2)
      celwin.dispose
      pbTweening(canvas)
      break
    end
    if celwin.changed?(4)
      celwin.dispose
      pbCellBatch(canvas)
      break
    end
    if celwin.changed?(6)
      celwin.dispose
      pbCelRotateFlip(canvas)
      break
    end
    if celwin.changed?(8)
      celwin.dispose
      pbFlipAroundPoint(canvas)
      break
    end
    if celwin.changed?(10)
      celwin.dispose
      pbRenumberBatch(canvas)
      break
    end
  end
end

def pbFrameMenu(canvas)
  framewin = ControlWindow.new(440, 0, 200, 512) # Can change if things overlap
  framewin.viewport = canvas.viewport
  framewin.addButton(_INTL("Copy Frames"))
  framewin.addSpace
  framewin.addButton(_INTL("Clear Frames"))
  framewin.addSpace
  framewin.addButton(_INTL("Entire Slide"))
  framewin.addSpace
  framewin.addButton(_INTL("Reverse Animation"))
  framewin.addSpace
  framewin.addButton(_INTL("Convert Old Self-Target"))
  loop do
    Graphics.update
    Input.update
    framewin.update
    if (Input.triggerex?(Input::LeftMouseKey) && Mouse::getMousePos[0] < 440) || Input.trigger?(Input::B)
      framewin.dispose
      break
    end
    if framewin.changed?(0)
      framewin.dispose
      pbCopyFrames(canvas)
      break
    end
    if framewin.changed?(2)
      framewin.dispose
      pbClearFrames(canvas)
      break
    end
    if framewin.changed?(4)
      framewin.dispose
      pbEntireSlide(canvas)
      break
    end
    if framewin.changed?(6)
      framewin.dispose
      pbReverseAnim(canvas)
      break
    end
    if framewin.changed?(8)
      framewin.dispose
      pbConvertSelfTarget(canvas)
      break
    end
  end
end

def pbGlobalAnimSettings(canvas, animation)
  settingswin = ControlWindow.new(0, 0, 640, 320)
  settingswin.viewport = canvas.viewport
  settingswin.opacity = 200
  option1 = settingswin.addCheckbox(_INTL("Flip user/target on opp?"))
  option2 = settingswin.addCheckbox(_INTL("Flip foreground movement on opp?"))
  okbutton = settingswin.addButton(_INTL("OK"))
  cancelbutton = settingswin.addButton(_INTL("Cancel"))
  settingswin.controls[0].curvalue = animation[animation.selected].oppflip ? animation[animation.selected].oppflip : false
  settingswin.controls[1].curvalue = animation[animation.selected].foreflip ? animation[animation.selected].foreflip : false
  loop do
    Graphics.update
    Input.update
    settingswin.update
    if settingswin.changed?(okbutton)
      animation[animation.selected].oppflip = settingswin.value(option1)
      animation[animation.selected].foreflip = settingswin.value(option2)
      settingswin.invalidate
      break
    end
    if settingswin.changed?(cancelbutton) || Input.trigger?(Input::B)
      break
    end
  end
  settingswin.dispose
end

################################################################################
# Debug option for rearranging animations
################################################################################
def pbAnimationsOrganiser
  list = loadAnimations
  if !list || !list[0]
    Kernel.pbMessage(_INTL("No animations exist."))
    return
  end
  viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  viewport.z = 99999
  cmdwin = pbListWindow([], 256)
  cmdwin.viewport = viewport
  cmdwin.z = 2
  title = Window_UnformattedTextPokemon.new(_INTL("Animations Organiser"))
  title.x = 256
  title.y = 0
  title.width = Graphics.width - 256
  title.height = 64
  title.viewport = viewport
  title.z = 2
  info = Window_AdvancedTextPokemon.new(_INTL("Z+Up/Down: Swap\nZ+Left: Delete\nZ+Right: Insert"))
  info.x = 256
  info.y = 64
  info.width = Graphics.width - 256
  info.height = Graphics.height - 64
  info.viewport = viewport
  info.z = 2
  commands = []
  refreshlist = true
  cmd = [0, 0]
  loop do
    if refreshlist
      commands = []
      for i in 0...list.length
        commands.push(sprintf("%d: %s", i, list[i] ? list[i].name : "???"))
      end
    end
    refreshlist = false
    cmd = pbCommands3(cmdwin, commands, -1, cmd[1], true)
    if cmd[0] == 1 # Swap animation up
      if cmd[1] >= 0 && cmd[1] < commands.length - 1
        list[cmd[1] + 1], list[cmd[1]] = list[cmd[1]], list[cmd[1] + 1]
        refreshlist = true
      end
    elsif cmd[0] == 2 # Swap animation down
      if cmd[1] > 0
        list[cmd[1] - 1], list[cmd[1]] = list[cmd[1]], list[cmd[1] - 1]
        refreshlist = true
      end
    elsif cmd[0] == 3 # Delete spot
      list[cmd[1]] = nil
      list.compact
      cmd[1] = [cmd[1], list.length - 1].min
      refreshlist = true
      pbWait(5)
    elsif cmd[0] == 4 # Insert spot
      list[list.length] = PBAnimation.new
      i = list.length - 1
      loop do
        break unless i > cmd[1]

        list[i], list[i - 1] = list[i - 1], list[i]
        i -= 1
      end
      refreshlist = true
    elsif cmd[0] == 0
      cmd2 = Kernel.pbMessage(
        _INTL("Save changes?"),
        [_INTL("Yes"), _INTL("No"), _INTL("Cancel")],
        3
      )
      if cmd2 == 0 || cmd2 == 1
        if cmd2 == 0
          # Save animations here
          saveAnimations(list, all: true)
          Kernel.pbMessage(_INTL("Data saved."))
        end
        break
      end
    end
  end
  title.dispose
  info.dispose
  cmdwin.dispose
  viewport.dispose
end

def pbConvertAnimToNewFormat(textdata)
  needconverting = false
  for i in 0...textdata.length
    next if !textdata[i]

    for j in 0...PBAnimation::MAXSPRITES
      next if !textdata[i][j]

      needconverting = true if textdata[i][j][AnimFrame::FOCUS] == nil
      break if needconverting
    end
    break if needconverting
  end
  if needconverting
    for i in 0...textdata.length
      next if !textdata[i]

      for j in 0...PBAnimation::MAXSPRITES
        next if !textdata[i][j]

        textdata[i][j][AnimFrame::PRIORITY] = 1 if textdata[i][j][AnimFrame::PRIORITY] == nil
        if j == 0 # User battler
          textdata[i][j][AnimFrame::FOCUS] = 2
          textdata[i][j][AnimFrame::X] = PBScene::FOCUSUSER_X
          textdata[i][j][AnimFrame::Y] = PBScene::FOCUSUSER_Y
        elsif j == 1 # Target battler
          textdata[i][j][AnimFrame::FOCUS] = 1
          textdata[i][j][AnimFrame::X] = PBScene::FOCUSTARGET_X
          textdata[i][j][AnimFrame::Y] = PBScene::FOCUSTARGET_Y
        else
          textdata[i][j][AnimFrame::FOCUS] = (textdata.position || 4)
          if textdata.position == 1
            textdata[i][j][AnimFrame::X] += PBScene::FOCUSTARGET_X
            textdata[i][j][AnimFrame::Y] += PBScene::FOCUSTARGET_Y - 2
          end
        end
      end
    end
  end
  return needconverting
end

def pbConvertAnimsToNewFormat
  Kernel.pbMessage(_INTL("Will convert animations now."))
  count = 0
  animations = loadAnimations(all: true)
  if !animations || !animations[0]
    Kernel.pbMessage(_INTL("No animations exist."))
    return
  end
  for k in 0...animations.length
    next if !animations[k]

    ret = pbConvertAnimToNewFormat(animations[k])
    count += 1 if ret
  end
  saveAnimations(animations, all: true) if count > 0
  $cache.animations = animations if count > 0
  Kernel.pbMessage(_INTL("{1} animations converted to new format.", count))
end

def animCompressor(animfile)
  framecount = 0
  nilsremoved = 0
  for i in 0...animfile.length
    for j in (1...animfile[i].length).to_a.reverse
      for k in 0...animfile[i][j].length
        curframe = animfile[i][j][k]
        next if curframe == nil

        prevframe = animfile[i][j - 1][k]
        next if prevframe != curframe

        animfile[i][j][k] = 0
        framecount += 1
      end
      for k in (0...animfile[i][j].length).to_a.reverse
        break if animfile[i][j][k] != nil

        animfile[i][j].delete_at(k)
        nilsremoved += 1
      end
    end
  end
  puts framecount
  puts nilsremoved
  return animfile
end

def saveAnimations(animations, all: false)
  Dir.mkdir("Animations") unless File.exist?("Animations")
  dumper = AnimationDumper.new
  for i in 0...animations.length
    animation = animations[i]
    name = sprintf("Anim%04d%s.rb", i, (animation.name != "" ? " - " + animation.name.sub(":", " ").gsub(/[^0-9A-Za-z ]/, "") : ""))
    next unless all || animations.loaded.include?(i) || !File.exist?("Animations/" + name)

    Dir.glob(sprintf("Animations/Anim%04d*.rb", i)).each { |file| File.delete(file) }
    File.write("Animations/" + name, dumper.dump(animation))
  end
  animations.clearLoaded
  save_data(animations, "Data/battleanims.dat")
  $cache.animations = animations
end

def loadAnimations(all: false)
  animations = all ? PBAnimations.new : $cache.animations
  animations[0] = nil if all
  factory = AnimationFactory.new
  Dir.glob('Animations/Anim*.rb') do |file|
    match = file.match(/^Animations\/Anim(?<index>[0-9]++)(?: - (?<name>.*))?\.rb$/)
    if match
      index = match["index"].to_i
      name = match["name"]
      if all || animations.length <= index || (animations[index].name == "" && name != "")
        if all && !animations[index].nil?
          raise sprintf("Two Animations/Anim%04d - *.rb file detected.", index)
        end

        animations[index] = factory.evaluate(File.read(file))
      end
    end
  end
  if animations[0].nil?
    raise "No animations found. Run convertAnimations first."
  end

  return animations
end

def convertAnimations
  saveAnimations($cache.animations, all: true)
end
