mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2026-11-03 00:06:01 +00:00
456 lines
13 KiB
Ruby
456 lines
13 KiB
Ruby
class MultiSelectOptionsScene
|
|
attr_reader :selected_items
|
|
attr_reader :canceled
|
|
|
|
SWITCH_FOCUS_BUTTONS = [Input::L, Input::R]
|
|
CURSOR_X_OFFSET = -4
|
|
ARROW_NORMAL = "Graphics/Pictures/selarrow"
|
|
ARROW_ACTIVE = "Graphics/Pictures/selarrow_red"
|
|
|
|
def initialize(items, preselected = [], title = "Select options",
|
|
item_name_proc = nil, filter_placeholder = "Shift to search",
|
|
item_weight_proc = nil)
|
|
@items = items
|
|
@item_name_proc = item_name_proc || proc { |item| item.to_s }
|
|
@item_weight_proc = item_weight_proc || proc { |_item| 1 }
|
|
@selected_items = preselected.dup
|
|
@selected_display_items = []
|
|
@focus = :full # :full or :selected
|
|
|
|
@title_text = title
|
|
@filter_placeholder = filter_placeholder
|
|
@canceled = false
|
|
end
|
|
|
|
def pbStartScene(window_x = 0, window_y = 0)
|
|
title_height = 60
|
|
filter_height = 60
|
|
|
|
left_width = Graphics.width / 2
|
|
right_x = window_x + left_width
|
|
right_width = Graphics.width - left_width
|
|
|
|
reference_text = @items.map { |i| @item_name_proc.call(i) }.max_by(&:length) || @filter_placeholder
|
|
reference_text = @filter_placeholder if @filter_placeholder.length > reference_text.length
|
|
|
|
@viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
|
|
@viewport.z = 99999
|
|
|
|
@sprites = {}
|
|
@sprites["title"] = Window_UnformattedTextPokemon.newWithSize(
|
|
_INTL(@title_text), window_x, window_y, Graphics.width, title_height, @viewport)
|
|
|
|
@sprites["filter"] = Window_UnformattedTextPokemon.newWithSize(
|
|
reference_text, window_x, window_y + title_height, left_width, filter_height, @viewport)
|
|
@sprites["filter"].setTextToFit(reference_text)
|
|
|
|
@sprites["option"] = Window_MultiSelectFilterList.new(
|
|
[], window_x, window_y + title_height + filter_height,
|
|
left_width, Graphics.height - (window_y + title_height + filter_height),
|
|
@viewport
|
|
)
|
|
@sprites["option"].active = true
|
|
pbSetSystemFont(@sprites["option"].contents)
|
|
|
|
# Right-side panel: selected items + running total
|
|
@sprites["selected_title"] = Window_UnformattedTextPokemon.newWithSize(
|
|
"", right_x, window_y + title_height, right_width, filter_height, @viewport)
|
|
|
|
@sprites["selected_list"] = Window_ReadOnlyList.new(
|
|
[], right_x, window_y + title_height + filter_height,
|
|
right_width, Graphics.height - (window_y + title_height + filter_height),
|
|
@viewport
|
|
)
|
|
@sprites["selected_list"].active = false
|
|
@sprites["selected_list"].index = 0
|
|
|
|
@arrow_paths = {}
|
|
@sprites["cursor"] = IconSprite.new(0, 0, @viewport)
|
|
@sprites["cursor"].setBitmap(ARROW_ACTIVE)
|
|
@arrow_paths[@sprites["cursor"]] = ARROW_ACTIVE
|
|
@sprites["cursor"].z = 99999
|
|
|
|
@sprites["cursor2"] = IconSprite.new(0, 0, @viewport)
|
|
@sprites["cursor2"].setBitmap(ARROW_NORMAL)
|
|
@arrow_paths[@sprites["cursor2"]] = ARROW_NORMAL
|
|
@sprites["cursor2"].z = 99999
|
|
|
|
@filterText = ""
|
|
@filteredItems = []
|
|
refreshList
|
|
end
|
|
|
|
# ---------------------------------------------------------------- cursors
|
|
|
|
def updateCursorPosition
|
|
positionArrow(@sprites["cursor"], @sprites["option"], @focus == :full)
|
|
positionArrow(@sprites["cursor2"], @sprites["selected_list"], @focus == :selected)
|
|
@sprites["cursor2"].visible = false if @selected_items.empty?
|
|
end
|
|
|
|
def positionArrow(arrow, win, focused)
|
|
rect = win.itemRect(win.index)
|
|
if rect.width == 0 && rect.height == 0 # index out of visible range
|
|
arrow.visible = false
|
|
return
|
|
end
|
|
|
|
# Red arrow for the active list, normal arrow for the other
|
|
path = focused ? ARROW_ACTIVE : ARROW_NORMAL
|
|
if @arrow_paths[arrow] != path
|
|
arrow.setBitmap(path)
|
|
@arrow_paths[arrow] = path
|
|
end
|
|
|
|
arrow_height = arrow.bitmap ? arrow.bitmap.height : 0
|
|
y_center_offset = (win.rowHeight - arrow_height) / 2
|
|
|
|
arrow.x = win.x + win.startX + rect.x + CURSOR_X_OFFSET
|
|
arrow.y = win.y + win.startY + rect.y + y_center_offset
|
|
arrow.opacity = focused ? 255 : 110
|
|
arrow.visible = true
|
|
end
|
|
|
|
# ---------------------------------------------------------------- focus
|
|
|
|
def setFocus(focus)
|
|
focus = :full if focus == :selected && @selected_items.empty?
|
|
@focus = focus
|
|
@sprites["option"].active = (@focus == :full)
|
|
@sprites["selected_list"].active = (@focus == :selected)
|
|
syncFullListToSelected if @focus == :selected
|
|
updateCursorPosition
|
|
end
|
|
|
|
def toggleFocus
|
|
pbSEPlay("GUI naming tab swap start")
|
|
setFocus(@focus == :full ? :selected : :full)
|
|
end
|
|
|
|
def focusLatestSelected
|
|
@sprites["selected_list"].index = 0
|
|
updateCursorPosition
|
|
end
|
|
|
|
def syncFullListToSelected
|
|
item = @selected_display_items[@sprites["selected_list"].index]
|
|
return if item.nil?
|
|
idx = @filteredItems.index(item)
|
|
return if idx.nil? # item is hidden by the current search filter
|
|
@sprites["option"].index = itemStartIndex + idx
|
|
updateCursorPosition
|
|
end
|
|
|
|
def removeFromSelected(list_index)
|
|
item = @selected_display_items[list_index]
|
|
return if item.nil?
|
|
@selected_items.delete(item)
|
|
pbSEPlay("GUI naming tab swap end")
|
|
keep_index = @sprites["selected_list"].index
|
|
refreshList
|
|
|
|
max_index = [@selected_items.length - 1, 0].max
|
|
@sprites["selected_list"].index = [keep_index, max_index].min
|
|
|
|
setFocus(:full)
|
|
idx = @filteredItems.index(item)
|
|
@sprites["option"].index = itemStartIndex + idx if idx
|
|
updateCursorPosition
|
|
end
|
|
|
|
# ---------------------------------------------------------------- indices
|
|
|
|
def cancelIndex; return 0; end
|
|
def selectAllIndex; return 1; end
|
|
def confirmIndex; return 2; end
|
|
def itemStartIndex; return 3; end
|
|
|
|
# ---------------------------------------------------------------- refresh
|
|
|
|
def refreshList
|
|
filtered = @filterText.empty? ? @items.dup : @items.select { |i|
|
|
@item_name_proc.call(i).downcase.include?(@filterText.downcase)
|
|
}
|
|
@filteredItems = filtered
|
|
|
|
display = []
|
|
flags = []
|
|
|
|
display.push(_INTL("Cancel"))
|
|
flags.push(false)
|
|
|
|
all_selected = filtered.length > 0 && filtered.all? { |i| @selected_items.include?(i) }
|
|
display.push(all_selected ? _INTL("Unselect All") : _INTL("Select All"))
|
|
flags.push(false)
|
|
|
|
display.push(_INTL("Confirm"))
|
|
flags.push(false)
|
|
|
|
filtered.each do |i|
|
|
display.push(@item_name_proc.call(i))
|
|
flags.push(@selected_items.include?(i))
|
|
end
|
|
|
|
old_index = @sprites["option"].index || 0
|
|
@sprites["option"].commands = display
|
|
@sprites["option"].selected_flags = flags
|
|
@sprites["option"].confirm_index = confirmIndex
|
|
@sprites["option"].selall_index = selectAllIndex
|
|
@sprites["option"].cancel_index = cancelIndex
|
|
@sprites["option"].index = [old_index, display.length - 1].min
|
|
@sprites["option"].refresh
|
|
|
|
@sprites["filter"].text = @filterText.empty? ? @filter_placeholder : @filterText
|
|
refreshSelectedPanel
|
|
updateCursorPosition
|
|
end
|
|
|
|
def refreshSelectedPanel
|
|
total_weight = @selected_items.sum { |i| @item_weight_proc.call(i) }
|
|
@sprites["selected_title"].text = _INTL("{1} selected sprites", total_weight)
|
|
|
|
@selected_display_items = @selected_items.reverse
|
|
lines = @selected_display_items.map { |i| @item_name_proc.call(i) }
|
|
lines = [_INTL("")] if lines.empty?
|
|
|
|
old_index = @sprites["selected_list"].index || 0
|
|
@sprites["selected_list"].commands = lines
|
|
@sprites["selected_list"].index = [old_index, lines.length - 1].min
|
|
@sprites["selected_list"].refresh
|
|
end
|
|
|
|
def pbUpdate
|
|
pbUpdateSpriteHash(@sprites)
|
|
end
|
|
|
|
# ---------------------------------------------------------------- main loop
|
|
|
|
def pbOptions
|
|
loop do
|
|
Graphics.update
|
|
Input.update
|
|
|
|
if SWITCH_FOCUS_BUTTONS.any? { |b| Input.trigger?(b) }
|
|
toggleFocus
|
|
next
|
|
end
|
|
|
|
if Input.trigger?(Input::ACTION)
|
|
pbStartSearchEntry
|
|
next
|
|
end
|
|
|
|
if Input.trigger?(Input::BACK)
|
|
@canceled = true
|
|
break
|
|
end
|
|
|
|
if @focus == :selected
|
|
old_index = @sprites["selected_list"].index
|
|
@sprites["selected_list"].update
|
|
syncFullListToSelected if @sprites["selected_list"].index != old_index
|
|
updateCursorPosition
|
|
|
|
if Input.trigger?(Input::USE)
|
|
removeFromSelected(@sprites["selected_list"].index)
|
|
end
|
|
next
|
|
end
|
|
|
|
# ---- full list focused ----
|
|
old_index = @sprites["option"].index
|
|
@sprites["option"].update
|
|
updateCursorPosition if @sprites["option"].index != old_index
|
|
|
|
if Input.repeat?(Input::RIGHT)
|
|
pbSEPlay("GUI party switch")
|
|
scroll_amount = 8
|
|
last = @sprites["option"].itemCount - 1
|
|
if @sprites["option"].index + scroll_amount >= last
|
|
@sprites["option"].index = last
|
|
else
|
|
@sprites["option"].index += scroll_amount
|
|
end
|
|
updateCursorPosition
|
|
next
|
|
end
|
|
|
|
if Input.repeat?(Input::LEFT)
|
|
pbSEPlay("GUI party switch")
|
|
scroll_amount = -8
|
|
if @sprites["option"].index + scroll_amount <= 0
|
|
scrollToConfirm
|
|
else
|
|
@sprites["option"].index += scroll_amount
|
|
end
|
|
next
|
|
end
|
|
|
|
if Input.trigger?(Input::USE)
|
|
index = @sprites["option"].index
|
|
|
|
if index == cancelIndex
|
|
@canceled = true
|
|
break
|
|
elsif index == selectAllIndex
|
|
all_selected = @filteredItems.length > 0 && @filteredItems.all? { |i| @selected_items.include?(i) }
|
|
if all_selected
|
|
@selected_items -= @filteredItems
|
|
else
|
|
@filteredItems.each do |item|
|
|
@selected_items << item unless @selected_items.include?(item)
|
|
end
|
|
end
|
|
pbSEPlay("GUI naming confirm")
|
|
refreshList
|
|
focusLatestSelected
|
|
elsif index == confirmIndex
|
|
break
|
|
else
|
|
item = @filteredItems[index - itemStartIndex]
|
|
if @selected_items.include?(item)
|
|
@selected_items.delete(item)
|
|
pbSEPlay("GUI naming tab swap end")
|
|
else
|
|
@selected_items << item
|
|
pbSEPlay("GUI naming confirm")
|
|
end
|
|
refreshList
|
|
focusLatestSelected
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def scrollToConfirm
|
|
@sprites["option"].index = confirmIndex
|
|
updateCursorPosition
|
|
end
|
|
|
|
def pbStartSearchEntry
|
|
if $PokemonSystem.textinput == 1 # keyboard
|
|
scene = PokedexTextEntry.new
|
|
else
|
|
scene = PokemonEntryScene2.new
|
|
end
|
|
scene.pbStartScene(
|
|
_INTL("Search:"),
|
|
0, # min length (0 allows clearing the filter)
|
|
30, # max length
|
|
@filterText
|
|
)
|
|
query = scene.pbEntry
|
|
scene.pbEndScene
|
|
|
|
if query
|
|
@filterText = query
|
|
refreshList
|
|
syncFullListToSelected if @focus == :selected
|
|
end
|
|
end
|
|
|
|
def pbEndScene
|
|
pbPlayCloseMenuSE
|
|
pbDisposeSpriteHash(@sprites)
|
|
@viewport.dispose
|
|
end
|
|
end
|
|
|
|
class MultiSelectOptionScreen
|
|
def initialize(scene)
|
|
@scene = scene
|
|
end
|
|
|
|
def pbStartScreen(window_x = 0, window_y = 0)
|
|
@scene.pbStartScene(window_x, window_y)
|
|
@scene.pbOptions
|
|
@scene.pbEndScene
|
|
return nil if @scene.canceled
|
|
return @scene.selected_items
|
|
end
|
|
end
|
|
|
|
class Window_MultiSelectFilterList < Window_DrawableCommand
|
|
attr_accessor :commands
|
|
attr_accessor :selected_flags
|
|
attr_accessor :confirm_index
|
|
attr_accessor :selall_index
|
|
attr_accessor :cancel_index
|
|
|
|
def initialize(commands, x, y, width, height, viewport = nil)
|
|
@commands = commands
|
|
@selected_flags = []
|
|
@confirm_index = nil
|
|
@cancel_index = nil
|
|
@selall_index = nil
|
|
@selBaseColor = Color.new(48, 96, 216)
|
|
@selShadowColor = Color.new(32, 32, 32)
|
|
@confirmBaseColor = Color.new(64, 200, 96)
|
|
@confirmShadowColor = Color.new(24, 96, 40)
|
|
@cancelBaseColor = Color.new(216, 64, 64)
|
|
@cancelShadowColor = Color.new(96, 24, 24)
|
|
super(x, y, width, height, viewport)
|
|
end
|
|
|
|
def itemCount
|
|
return @commands.length
|
|
end
|
|
|
|
def drawCursor(index, rect)
|
|
return Rect.new(rect.x + 16, rect.y, rect.width - 16, rect.height)
|
|
end
|
|
|
|
def drawItem(index, _count, rect)
|
|
rect = drawCursor(index, rect)
|
|
text = @commands[index]
|
|
|
|
if index == @confirm_index
|
|
base, shadow = @confirmBaseColor, @confirmShadowColor
|
|
elsif index == @selall_index
|
|
base, shadow = @selBaseColor, @selShadowColor
|
|
elsif index == @cancel_index
|
|
base, shadow = @cancelBaseColor, @cancelShadowColor
|
|
elsif @selected_flags[index]
|
|
base, shadow = @selBaseColor, @selShadowColor
|
|
else
|
|
base, shadow = self.baseColor, self.shadowColor
|
|
end
|
|
|
|
pbDrawShadowText(self.contents, rect.x, rect.y, rect.width, rect.height, text, base, shadow)
|
|
end
|
|
|
|
def refresh
|
|
self.contents.clear if self.contents
|
|
super
|
|
end
|
|
end
|
|
|
|
# Used for the "Selected" panel.
|
|
class Window_ReadOnlyList < Window_DrawableCommand
|
|
attr_accessor :commands
|
|
|
|
def initialize(commands, x, y, width, height, viewport = nil)
|
|
@commands = commands
|
|
super(x, y, width, height, viewport)
|
|
self.active = false
|
|
end
|
|
|
|
def itemCount
|
|
return @commands.length
|
|
end
|
|
|
|
def drawCursor(index, rect)
|
|
return Rect.new(rect.x + 16, rect.y, rect.width - 16, rect.height)
|
|
end
|
|
|
|
def drawItem(index, _count, rect)
|
|
rect = drawCursor(index, rect)
|
|
pbDrawShadowText(self.contents, rect.x, rect.y, rect.width, rect.height,
|
|
@commands[index], self.baseColor, self.shadowColor)
|
|
end
|
|
|
|
def refresh
|
|
self.contents.clear if self.contents
|
|
super
|
|
end
|
|
end |