feat: Introduce colored text windows and enhanced positioning
This commit introduces a significant enhancement to the DynamicCursesInput gem, enabling the creation of colored text windows within the terminal. The new `DynamicCursesInput::ColorWindow` class provides methods to add colored text to the window and handle user input. Notable changes in this commit include: - Implementation of colored windows with customizable color schemes and text content. - Automatic centering of text within the window when 'x' is set to 'center.' - Fine-tuning of centered positioning to shift back by 12 cells for improved visual layout. - Removal of Fiber reliance and adoption of instance methods to resolve positioning issues. - Compatibility fixes for different terminal environments to ensure consistent behavior. Additionally, bug fixes have been made to resolve issues related to text misalignment and window positioning. This feature-rich update aims to improve the user experience and offer better control over window positioning and color schemes. We hope these enhancements will be beneficial to our users. Feedback and suggestions are always welcome!
This commit is contained in:
parent
5bf14c982a
commit
a27f41a601
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@
|
|||
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
buildgem.sh
|
||||
|
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -1,5 +1,30 @@
|
|||
## [Unreleased]
|
||||
## [Release]
|
||||
|
||||
## [0.1.0] - 2023-06-07
|
||||
## [1.0.0] - 2023-06-07
|
||||
|
||||
- Initial release
|
||||
|
||||
## [Release]
|
||||
|
||||
## [1.1.0] - 2023-07-26
|
||||
|
||||
- Features and Enhancements:
|
||||
|
||||
- Added support for printing colored windows with customized positions and color schemes.
|
||||
- Introduced the DynamicCursesInput::ColorWindow class, which allows creating colored text windows within the terminal.
|
||||
- The ColorWindow class provides methods to add colored text to the window and handle user input.
|
||||
- Implemented automatic centering of text within the window when x is set to 'center'.
|
||||
- Adjusted the centered position to shift back by 12 cells for better visual layout when necessary.
|
||||
- Removed reliance on Fibers and replaced it with instance methods to resolve issues with positioning.
|
||||
- Handled compatibility issues with different terminal environments to ensure consistent behavior.
|
||||
- Refactored the code to eliminate unnecessary checks for IRB, enabling smooth execution in various contexts.
|
||||
- Improved the debug log functionality for easier debugging and troubleshooting.
|
||||
|
||||
- Bug Fixes:
|
||||
|
||||
- Fixed the issue causing text to be misaligned or misplaced in certain terminal environments.
|
||||
- Resolved a bug where the window position was not being updated correctly in some cases.
|
||||
|
||||
- Other Changes:
|
||||
|
||||
- Removed redundant and unused code snippets to improve code cleanliness and maintainability.
|
||||
|
|
10
Gemfile
10
Gemfile
|
@ -1,12 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in dynamic_curses_input.gemspec
|
||||
gemspec
|
||||
|
||||
gem "rake"
|
||||
gem 'rake'
|
||||
|
||||
gem "rubocop"
|
||||
gem 'rubocop'
|
||||
|
||||
gem "curses"
|
||||
gem 'curses'
|
||||
|
||||
gem 'reline'
|
||||
|
|
38
Gemfile.lock
38
Gemfile.lock
|
@ -1,44 +1,38 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
dynamic_curses_input (1.0.0)
|
||||
dynamic_curses_input (1.0.1.1)
|
||||
curses
|
||||
reline
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.2)
|
||||
curses (1.4.4)
|
||||
diff-lcs (1.5.0)
|
||||
io-console (0.6.0)
|
||||
json (2.6.3)
|
||||
language_server-protocol (3.17.0.3)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.1)
|
||||
parser (3.2.2.3)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
racc (1.7.1)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
regexp_parser (2.8.0)
|
||||
regexp_parser (2.8.1)
|
||||
reline (0.3.6)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.2.5)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
rspec-expectations (~> 3.12.0)
|
||||
rspec-mocks (~> 3.12.0)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.52.0)
|
||||
rubocop (1.55.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
parser (>= 3.2.2.3)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
rubocop-ast (>= 1.28.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.29.0)
|
||||
|
@ -53,8 +47,8 @@ DEPENDENCIES
|
|||
curses
|
||||
dynamic_curses_input!
|
||||
rake
|
||||
rspec
|
||||
reline
|
||||
rubocop
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.13
|
||||
2.4.16
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "lib/dynamic_curses_input/version"
|
||||
require_relative 'lib/dynamic_curses_input/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "dynamic_curses_input"
|
||||
spec.name = 'dynamic_curses_input'
|
||||
spec.version = DynamicCursesInput::VERSION
|
||||
spec.authors = ["VetheonGames"]
|
||||
spec.email = ["vetheon@pixelatedstudios.net"]
|
||||
spec.authors = ['VetheonGames']
|
||||
spec.email = ['vetheon@pixelatedstudios.net']
|
||||
|
||||
spec.summary = "A simple library for making Curses TUI input more dynamic and user-friendly"
|
||||
spec.summary = 'A simple library for making Curses TUI input more dynamic and user-friendly'
|
||||
spec.description = "Dynamic Curses Input is a highly simple, yet powerful gem that allows simple implementation of
|
||||
dynamic typing in curses TUI menus built in Ruby. For example, one can't simply use their arrow
|
||||
keys to navigate and edit inputs in Cursese TUI menus without adding a bunch of extra code to your
|
||||
|
@ -17,23 +17,23 @@ Gem::Specification.new do |spec|
|
|||
allowing the special keys to work as the average user would expect.
|
||||
IE: When you press the left arrow key, the cursor moves to the left and allows you to delete a
|
||||
character you entered that isn't the last character you entered."
|
||||
spec.homepage = "https://github.com/Pixelated-Studios/dynamic_curses_input"
|
||||
spec.license = "MIT"
|
||||
spec.required_ruby_version = "3.2.2"
|
||||
spec.homepage = 'https://github.com/Pixelated-Studios/dynamic_curses_input'
|
||||
spec.license = 'MIT'
|
||||
spec.required_ruby_version = '3.2.2'
|
||||
|
||||
spec.metadata["homepage_uri"] = spec.homepage
|
||||
spec.metadata["source_code_uri"] = "https://github.com/Pixelated-Studios/dynamic_curses_input"
|
||||
spec.metadata["changelog_uri"] = "https://github.com/Pixelated-Studios/dynamic_curses_input/blob/main/CHANGELOG.md"
|
||||
spec.metadata['homepage_uri'] = spec.homepage
|
||||
spec.metadata['source_code_uri'] = 'https://github.com/Pixelated-Studios/dynamic_curses_input'
|
||||
spec.metadata['changelog_uri'] = 'https://github.com/Pixelated-Studios/dynamic_curses_input/blob/main/CHANGELOG.md'
|
||||
|
||||
# Specify which files should be added to the gem when it is released.
|
||||
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
||||
spec.files = Dir.chdir(__dir__) do
|
||||
`git ls-files -z`.split("\x0").reject do |f|
|
||||
f.start_with?("spec", ".rspec") || (File.expand_path(f) == __FILE__)
|
||||
end
|
||||
end
|
||||
spec.require_paths = ["lib"]
|
||||
spec.files = Dir.glob('{bin,lib,sig}/**/*') + Dir.glob('*').reject { |f| f.start_with?('spec', '.rspec', 'dynamic_curses_input.gemspec') }
|
||||
spec.files << 'LICENSE.txt'
|
||||
spec.files << 'README.md'
|
||||
spec.files << 'dynamic_curses_input.gemspec'
|
||||
|
||||
spec.add_dependency "curses"
|
||||
spec.add_development_dependency "rubocop"
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_dependency 'curses'
|
||||
spec.add_dependency 'reline'
|
||||
spec.add_development_dependency 'rubocop'
|
||||
end
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
# lib/dynamic_curses_input.rb
|
||||
|
||||
require_relative "dynamic_curses_input/version"
|
||||
require_relative "dynamic_curses_input/input_handler"
|
||||
require 'readline' # Add the Readline module
|
||||
require_relative 'dynamic_curses_input/version'
|
||||
require_relative 'dynamic_curses_input/input_handler'
|
||||
require_relative 'dynamic_curses_input/color_window'
|
||||
|
||||
# The module entrypoint for our Gem
|
||||
module DynamicCursesInput
|
||||
|
@ -13,12 +15,74 @@ module DynamicCursesInput
|
|||
InputHandler.catch_input(echo)
|
||||
end
|
||||
|
||||
def self.ask_question(question, echo)
|
||||
def self.ask_question(color = 'white', question, x: 'center', input: true, echo: nil)
|
||||
Curses.clear
|
||||
Curses.setpos(1, 0)
|
||||
Curses.addstr(question)
|
||||
ColorWindow.add_color_window(color, question, y:, x:, input:, echo:)
|
||||
end
|
||||
|
||||
def self.print_color_window(color, text, y_value: nil, x: 'center', input: nil, echo: true)
|
||||
case x
|
||||
when 'center'
|
||||
terminal_size = `stty size`.split.map(&:to_i)
|
||||
y_value = terminal_size[0] / 2
|
||||
x_value = terminal_size[1] / 2 - text.length / 2 # Adjust x-coordinate to center the window
|
||||
x_value -= 12 if x_value > 1
|
||||
# the above line shifts the X value of the cell coords back by 12 cells if we are trying to center the window
|
||||
# we have to do this because math gets kind of approximate when we convert pixel ratios to character cell coords
|
||||
when 'left'
|
||||
y_value = Curses.lines / 2
|
||||
x_value = 0
|
||||
when 'right'
|
||||
y_value = Curses.lines / 2
|
||||
x_value = Curses.cols - text.length
|
||||
when 'left_center'
|
||||
y_value = Curses.lines / 4
|
||||
x_value = 0
|
||||
when 'right_center'
|
||||
y_value = Curses.lines / 4
|
||||
x_value = Curses.cols - text.length
|
||||
else
|
||||
y_value, x_value = x.split('px').map(&:to_i)
|
||||
end
|
||||
|
||||
# Initialize curses and get the terminal size
|
||||
Curses.init_screen
|
||||
Curses.start_color
|
||||
Curses.refresh
|
||||
catch_input(echo)
|
||||
|
||||
# Set up Readline for proper terminal settings
|
||||
setup_readline
|
||||
|
||||
ColorWindow.new(echo, x_value, y_value).add_color_window(color, text, x_value, y_value, input:, echo:)
|
||||
end
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
def process_print_color_window_args(args)
|
||||
case args.size
|
||||
when 2
|
||||
['white', args[0], args[1], 'center', nil, true]
|
||||
when 3
|
||||
['white', args[0], args[1], args[2], nil, true]
|
||||
when 4
|
||||
['white', args[0], args[1], args[2], args[3], true]
|
||||
when 5
|
||||
[args[0], args[1], args[2], args[3], args[4]]
|
||||
else
|
||||
raise ArgumentError, 'print_color_window accepts 2 to 5 arguments: color, text, [position], [input], [echo]'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.setup_readline
|
||||
# Set up Readline for proper terminal settings
|
||||
Readline.emacs_editing_mode
|
||||
# the above line sets Readline to emacs_editing_mode so that terminals behave like they're supposed to
|
||||
Readline.completion_append_character = ' '
|
||||
# we remove Readlines thing where it adds a space to the end of tab completes because it breaks the Curses cursor
|
||||
Readline.completion_proc = proc { |_s| [] }
|
||||
# here we basically are disabling tab completion all together. That's because for some reason it breaks the cursor
|
||||
end
|
||||
end
|
||||
|
||||
|
|
77
lib/dynamic_curses_input/color_window.rb
Normal file
77
lib/dynamic_curses_input/color_window.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# lib/dynamic_curses_input/color_window.rb
|
||||
|
||||
require 'curses'
|
||||
require_relative 'input_handler'
|
||||
|
||||
module DynamicCursesInput
|
||||
# Class for creating a colored window
|
||||
class ColorWindow
|
||||
# Initialize instance variables and setup curses
|
||||
def initialize(echo, x, y)
|
||||
@echo = echo # Determines whether input should be echoed to the screen
|
||||
setup_curses_color # Setup curses
|
||||
@x = x
|
||||
@y = y
|
||||
# Define color pairs
|
||||
Curses.init_pair(1, Curses::COLOR_BLACK, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(2, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(3, Curses::COLOR_GREEN, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(4, Curses::COLOR_CYAN, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(5, Curses::COLOR_RED, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(6, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK)
|
||||
Curses.init_pair(7, Curses::COLOR_YELLOW, Curses::COLOR_BLACK) # Brown is usually represented as yellow
|
||||
Curses.init_pair(8, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
|
||||
end
|
||||
|
||||
# Method that adds colored text to the window
|
||||
def add_color_window(color, text, x, y, input: nil, echo: true)
|
||||
# Map color names to color pair numbers
|
||||
color_map = {
|
||||
'black' => 1,
|
||||
'blue' => 2,
|
||||
'green' => 3,
|
||||
'cyan' => 4,
|
||||
'red' => 5,
|
||||
'magenta' => 6,
|
||||
'brown' => 7, # Brown is usually represented as yellow in terminal colors
|
||||
'white' => 8
|
||||
}
|
||||
|
||||
# Get the color pair number for the specified color
|
||||
color_pair = color_map[color.downcase]
|
||||
|
||||
# Set the cursor position if both x and y are specified
|
||||
if x && y
|
||||
set_position(y, x)
|
||||
elsif x.nil? && y.nil?
|
||||
# If both x and y are not specified, raise an ArgumentError
|
||||
raise ArgumentError, 'Both x and y coordinates must be specified for printing the color window.'
|
||||
end
|
||||
|
||||
# Print the text in the specified color
|
||||
Curses.attron(Curses.color_pair(color_pair))
|
||||
Curses.addstr(text)
|
||||
Curses.attroff(Curses.color_pair(color_pair))
|
||||
|
||||
# If an input is specified, take input from the user
|
||||
InputHandler.catch_input(echo) if input
|
||||
|
||||
Curses.refresh
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Setup curses
|
||||
def setup_curses_color
|
||||
Curses.init_screen
|
||||
Curses.start_color
|
||||
end
|
||||
|
||||
# Set cursor position manually on the X and Y axis
|
||||
def set_position(y, x)
|
||||
Curses.setpos(y, x)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# lib/dynamic_curses_input/input_handler.rb
|
||||
|
||||
require "curses"
|
||||
require 'curses'
|
||||
|
||||
module DynamicCursesInput
|
||||
# our main class for handling input
|
||||
|
@ -15,7 +15,7 @@ module DynamicCursesInput
|
|||
# Initialize instance variables and setup curses
|
||||
def initialize(echo)
|
||||
@echo = echo # Determines whether input should be echoed to the screen
|
||||
@input = "" # Stores the input string
|
||||
@input = '' # Stores the input string
|
||||
@cursor_pos = 0 # Stores the current cursor position
|
||||
@initial_y = Curses.stdscr.cury # Stores the initial y-coordinate of the cursor
|
||||
@initial_x = Curses.stdscr.curx # Stores the initial x-coordinate of the cursor
|
||||
|
@ -84,7 +84,7 @@ module DynamicCursesInput
|
|||
# Redraw the input string
|
||||
def redraw_input
|
||||
Curses.setpos(@initial_y, @initial_x) # Move cursor to initial position
|
||||
Curses.addstr(" " * (Curses.cols - @initial_x)) # Clear line
|
||||
Curses.addstr(' ' * (Curses.cols - @initial_x)) # Clear line
|
||||
Curses.setpos(@initial_y, @initial_x) # Move cursor to initial position
|
||||
Curses.addstr(@input) if @echo # Draw input string if @echo is true
|
||||
Curses.setpos(@initial_y, @initial_x + @cursor_pos) # Move cursor to current position
|
||||
|
@ -102,6 +102,11 @@ module DynamicCursesInput
|
|||
def self.right(cursor_pos, length)
|
||||
cursor_pos == length ? cursor_pos : cursor_pos + 1
|
||||
end
|
||||
|
||||
# Set cursor position
|
||||
def self.set_position(y, x)
|
||||
Curses.setpos(y, x)
|
||||
end
|
||||
end
|
||||
|
||||
# Class for deleting characters
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DynamicCursesInput
|
||||
VERSION = "1.0.0"
|
||||
VERSION = '1.1.0'
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user