lib/tty/prompt/slider.rb
# frozen_string_literal: true
module TTY
# A class responsible for shell prompt interactions.
class Prompt
# A class responsible for gathering numeric input from range
#
# @api public
class Slider
HELP = "(Use %s arrow keys, press Enter to select)"
FORMAT = ":slider %s"
# Initailize a Slider
#
# @param [Prompt] prompt
# the prompt
# @param [Hash] options
# the options to configure this slider
# @option options [Integer] :min The minimum value
# @option options [Integer] :max The maximum value
# @option options [Integer] :step The step value
# @option options [String] :format The display format
#
# @api public
def initialize(prompt, **options)
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@choices = Choices.new
@min = options.fetch(:min, 0)
@max = options.fetch(:max, 10)
@step = options.fetch(:step, 1)
@default = options[:default]
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@format = options.fetch(:format) { FORMAT }
@quiet = options.fetch(:quiet) { @prompt.quiet }
@help = options[:help]
@show_help = options.fetch(:show_help) { :start }
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
@first_render = true
@done = false
end
# Change symbols used by this prompt
#
# @param [Hash] new_symbols
# the new symbols to use
#
# @api public
def symbols(new_symbols = (not_set = true))
return @symbols if not_set
@symbols.merge!(new_symbols)
end
# Setup initial active position
#
# @return [Integer]
#
# @api private
def initial
if @default.nil?
# no default - choose the middle option
choices.size / 2
elsif default_choice = choices.find_by(:name, @default)
# found a Choice by name - use it
choices.index(default_choice)
else
# default is the index number
@default - 1
end
end
# Default help text
#
# @api public
def default_help
arrows = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
sprintf(HELP, arrows)
end
# Set help text
#
# @param [String] text
#
# @api private
def help(text = (not_set = true))
return @help if !@help.nil? && not_set
@help = (@help.nil? && not_set) ? default_help : text
end
# Change when help is displayed
#
# @api public
def show_help(value = (not_set = true))
return @show_ehlp if not_set
@show_help = value
end
# @api public
def default(value)
@default = value
end
# @api public
def min(value)
@min = value
end
# @api public
def max(value)
@max = value
end
# @api public
def step(value)
@step = value
end
# Add a single choice
#
# @api public
def choice(*value, &block)
if block
@choices << (value << block)
else
@choices << value
end
end
# Add multiple choices
#
# @param [Array[Object]] values
# the values to add as choices
#
# @api public
def choices(values = (not_set = true))
if not_set
@choices
else
values.each { |val| @choices << val }
end
end
# @api public
def format(value)
@format = value
end
# Set quiet mode.
#
# @api public
def quiet(value)
@quiet = value
end
# Call the slider by passing question
#
# @param [String] question
# the question to ask
#
# @apu public
def call(question, possibilities = nil, &block)
@question = question
choices(possibilities) if possibilities
block.call(self) if block
# set up a Choices collection for min, max, step
# if no possibilities were supplied
choices((@min..@max).step(@step).to_a) if @choices.empty?
@active = initial
@prompt.subscribe(self) do
render
end
end
def keyleft(*)
@active -= 1 if @active > 0
end
alias keydown keyleft
def keyright(*)
@active += 1 if (@active + 1) < choices.size
end
alias keyup keyright
def keyreturn(*)
@done = true
end
alias keyspace keyreturn
alias keyenter keyreturn
private
# Check if help is shown only on start
#
# @api private
def help_start?
@show_help =~ /start/i
end
# Check if help is always displayed
#
# @api private
def help_always?
@show_help =~ /always/i
end
# Render an interactive range slider.
#
# @api private
def render
@prompt.print(@prompt.hide)
until @done
question = render_question
@prompt.print(question)
@prompt.read_keypress
refresh(question.lines.count)
end
@prompt.print(render_question) unless @quiet
answer
ensure
@prompt.print(@prompt.show)
end
# Clear screen
#
# @param [Integer] lines
# the lines to clear
#
# @api private
def refresh(lines)
@prompt.print(@prompt.clear_lines(lines))
end
# @return [Integer, String]
#
# @api private
def answer
choices[@active].value
end
# Render question with the slider
#
# @return [String]
#
# @api private
def render_question
header = ["#{@prefix}#{@question} "]
if @done
header << @prompt.decorate(choices[@active].to_s, @active_color)
header << "\n"
else
header << render_slider
end
if @first_render && (help_start? || help_always?) ||
(help_always? && !@done)
header << "\n" + @prompt.decorate(help, @help_color)
@first_render = false
end
header.join
end
# Render slider representation
#
# @return [String]
#
# @api private
def render_slider
slider = (@symbols[:line] * @active) +
@prompt.decorate(@symbols[:bullet], @active_color) +
(@symbols[:line] * (choices.size - @active - 1))
value = choices[@active].name
case @format
when Proc
@format.call(slider, value)
else
@format.gsub(":slider", slider) % [value]
end
end
end # Slider
end # Prompt
end # TTY