Source code for pygame_menu.widgets.widget.colorinput
"""
pygame-menu
https://github.com/ppizarror/pygame-menu
COLOR INPUT
Color input class, Widget created in top of TextInput that provides a textbox
for entering and previewing colors in RGB and HEX format.
"""
__all__ = [
# Main class
'ColorInput',
'ColorInputManager',
# Constants
'COLORINPUT_TYPE_HEX',
'COLORINPUT_TYPE_RGB',
'COLORINPUT_HEX_FORMAT_LOWER',
'COLORINPUT_HEX_FORMAT_NONE',
'COLORINPUT_HEX_FORMAT_UPPER',
# Type
'ColorInputColorType',
'ColorInputHexFormatType'
]
import math
import pygame
import pygame_menu
from abc import ABC
from pygame_menu.locals import INPUT_TEXT
from pygame_menu.utils import check_key_pressed_valid, make_surface
from pygame_menu.widgets.core.widget import AbstractWidgetManager, Widget
from pygame_menu.widgets.widget.textinput import TextInput
from pygame_menu._types import Union, List, NumberType, Any, Optional, CallbackType, \
Literal, Tuple3IntType, NumberInstance, EventVectorType, Callable
# Input modes
COLORINPUT_TYPE_HEX = 'hex'
COLORINPUT_TYPE_RGB = 'rgb'
# Apply format to hex color string
COLORINPUT_HEX_FORMAT_LOWER = 'lower'
COLORINPUT_HEX_FORMAT_NONE = 'none'
COLORINPUT_HEX_FORMAT_UPPER = 'upper'
# Custom typing
ColorInputColorType = Literal[COLORINPUT_TYPE_RGB, COLORINPUT_TYPE_HEX]
ColorInputHexFormatType = Literal[COLORINPUT_HEX_FORMAT_LOWER,
COLORINPUT_HEX_FORMAT_UPPER,
COLORINPUT_HEX_FORMAT_NONE]
# noinspection PyMissingOrEmptyDocstring
[docs]class ColorInput(TextInput): # lgtm [py/missing-call-to-init]
"""
Color input widget.
The callbacks receive the current value and all unknown keyword arguments,
where ``current_color=widget.get_value()``:
.. code-block:: python
onchange(current_color, **kwargs)
onreturn(current_color, **kwargs)
.. note::
This widget cannot select text as :py:class:`pygame_menu.widgets.TextInput`
does. Also, copy and paste is disabled.
.. note::
ColorInput accepts the same transformations as :py:class:`pygame_menu.widgets.TextInput`.
:param title: Color input title
:param colorinput_id: ID of the text input
:param color_type: Type of color input
:param cursor_switch_ms: Interval of cursor switch between off and on status. First status is ``off``
:param dynamic_width: If ``True`` the widget width changes if the previsualization color box is active or not
:param hex_format: Hex format string mode
:param input_separator: Divisor between RGB channels
:param input_underline: Character drawn under each number input
:param input_underline_vmargin: Vertical margin of underline in px
:param cursor_color: Color of cursor
:param onchange: Function when changing the values of the color text
:param onreturn: Function when pressing return on the color text input
:param onselect: Function when selecting the widget
:param prev_margin: Horizontal margin between the previsualization and the input text in px
:param prev_width_factor: Width of the previsualization box in terms of the height of the widget
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held
:param repeat_keys_interval_ms: Interval between key press repetition when held
:param repeat_mouse_interval_ms: Interval between mouse events when held
:param kwargs: Optional keyword arguments
"""
_auto_separator_pos: List[int]
_color_type: str
_dynamic_width: bool
_hex_format: str
_last_g: int
_last_r: int
_prev_margin: int
_previsualization_surface: Optional['pygame.Surface']
_separator: str
def __init__(
self,
title: Any,
colorinput_id: str = '',
color_type: ColorInputColorType = COLORINPUT_TYPE_RGB,
cursor_color: Tuple3IntType = (0, 0, 0),
cursor_switch_ms: NumberType = 500,
dynamic_width: bool = True,
hex_format: ColorInputHexFormatType = COLORINPUT_HEX_FORMAT_NONE,
input_separator: str = ',',
input_underline: str = '_',
input_underline_vmargin: int = 0,
onchange: CallbackType = None,
onreturn: CallbackType = None,
onselect: CallbackType = None,
prev_margin: int = 10,
prev_width_factor: NumberType = 3,
repeat_keys_initial_ms: NumberType = 450,
repeat_keys_interval_ms: NumberType = 80,
repeat_mouse_interval_ms: NumberType = 100,
*args,
**kwargs
) -> None:
assert isinstance(color_type, str)
assert isinstance(colorinput_id, str)
assert isinstance(dynamic_width, bool)
assert isinstance(hex_format, str)
assert isinstance(input_separator, str)
assert isinstance(input_underline, str)
assert isinstance(prev_margin, int)
assert isinstance(prev_width_factor, NumberInstance)
assert len(input_separator) == 1, 'input_separator must be a single char'
assert len(input_separator) != 0, 'input_separator cannot be empty'
assert prev_width_factor > 0, \
'previsualization width factor must be greater than zero'
assert input_separator not in ('0', '1', '2', '3', '4', '5', '6', '7', '8',
'9'), 'input_separator cannot be a number'
assert color_type in (COLORINPUT_TYPE_HEX, COLORINPUT_TYPE_RGB), \
f'color type must be "{COLORINPUT_TYPE_HEX}" or "{COLORINPUT_TYPE_RGB}"'
assert hex_format in (COLORINPUT_HEX_FORMAT_NONE, COLORINPUT_HEX_FORMAT_LOWER,
COLORINPUT_HEX_FORMAT_UPPER), \
'invalid hex format mode, it must be "none", "lower" or "upper"'
_maxchar = 0
self._color_type = color_type.lower()
if self._color_type == COLORINPUT_TYPE_RGB:
_maxchar = 11 # RRR,GGG,BBB
self._valid_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
input_separator]
elif self._color_type == COLORINPUT_TYPE_HEX:
_maxchar = 7 # #XXYYZZ
self._valid_chars = ['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E',
'f', 'F', '#', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9']
# noinspection PyArgumentEqualDefault
super(ColorInput, self).__init__(
copy_paste_enable=False,
cursor_color=cursor_color,
cursor_switch_ms=cursor_switch_ms,
cursor_selection_enable=False,
history=0,
input_type=INPUT_TEXT,
input_underline=input_underline,
input_underline_vmargin=input_underline_vmargin,
maxchar=_maxchar,
maxwidth=0,
onchange=onchange,
onreturn=onreturn,
onselect=onselect,
password=False,
repeat_keys_initial_ms=repeat_keys_initial_ms,
repeat_keys_interval_ms=repeat_keys_interval_ms,
repeat_mouse_interval_ms=repeat_mouse_interval_ms,
text_ellipsis='',
textinput_id=colorinput_id,
title=title,
valid_chars=self._valid_chars,
*args,
**kwargs
)
# Store inner variables
self._auto_separator_pos = [] # This stores indexes of auto separator added
self._dynamic_width = dynamic_width
self._hex_format = hex_format
self._separator = input_separator
# Previsualization surface, if -1 does not show
self._last_b = -1
self._last_g = -1
self._last_r = -1
self._prev_margin = prev_margin
self._prev_width_factor = prev_width_factor
self._previsualization_surface = None
# Disable parent callbacks
self._apply_widget_update_callback = False
# Disable alt+x
self._alt_x_enabled = False
def _apply_font(self) -> None:
super(ColorInput, self)._apply_font()
# Compute the size of the underline
if self._input_underline != '':
max_width = 0 # Max expected width
if self._color_type == COLORINPUT_TYPE_RGB:
max_width = self._font_render_string(
f'255{self._separator}255{self._separator}255'
).get_width()
else:
for i in ('a', 'b', 'c', 'd', 'e', 'f'):
max_width = max(
max_width,
self._font_render_string(f'#{i * 6}').get_width()
)
char = math.ceil(max_width / self._input_underline_size)
for i in range(10): # Find the best guess for
fw = self._font_render_string(self._input_underline * int(char)).get_width()
char += 1
if fw >= max_width:
break
self._input_underline_len = char
[docs] def clear(self) -> None:
super(ColorInput, self).clear()
self._previsualization_surface = None
self._auto_separator_pos = []
if self._color_type == COLORINPUT_TYPE_HEX:
super(ColorInput, self).set_value('#')
self.change()
[docs] def set_value(self, color: Optional[Union[str, Tuple3IntType]]) -> None:
"""
Set the color value.
:param color: A string if the type is HEX, or a (r, g, b) tuple if RGB
"""
if color is None:
color = ''
format_color = ''
if self._color_type == COLORINPUT_TYPE_RGB:
if color == '':
super(ColorInput, self).set_value('')
return
assert isinstance(color, tuple), \
'color in rgb format must be a tuple in (r,g,b) format'
assert len(color) == 3, 'tuple must contain only 3 colors, R,G,B'
r, g, b = color
assert isinstance(r, int), 'red color must be an integer'
assert isinstance(g, int), 'blue color must be an integer'
assert isinstance(b, int), 'green color must be an integer'
assert 0 <= r <= 255, 'red color must be between 0 and 255'
assert 0 <= g <= 255, 'blue color must be between 0 and 255'
assert 0 <= b <= 255, 'green color must be between 0 and 255'
format_color = f'{r}{self._separator}{g}{self._separator}{b}'
self._auto_separator_pos = [0, 1]
elif self._color_type == COLORINPUT_TYPE_HEX:
text = str(color).strip()
if text == '' or text == '#':
format_color = '#'
else:
# Remove all invalid chars
valid_text = ''
for ch in text:
if ch in self._valid_chars:
valid_text += ch
text = valid_text
# Check if the color is valid
count_hash = 0
for ch in text:
if ch == '#':
count_hash += 1
if count_hash == 1:
assert text[0] == '#', 'color format must be "#RRGGBB"'
if count_hash == 0:
text = '#' + text
assert len(text) == 7, \
'invalid color, only formats "#RRGGBB" or "RRGGBB" are allowed'
format_color = text
super(ColorInput, self).set_value(format_color)
self._format_hex()
[docs] def value_changed(self) -> bool:
default = self._default_value
if self._color_type == COLORINPUT_TYPE_HEX and '#' not in default:
default = '#' + default
return self.get_value(as_string=True) != default
[docs] def get_value(self, as_string: bool = False) -> Union[str, Tuple3IntType]:
"""
Return the color value as a tuple or red blue and green channels.
.. note::
If the data is invalid the widget returns ``(-1, -1, -1)``.
:param as_string: If ``True`` returns the widget value as plain text
:return: Color tuple as (R, G, B) or color string
"""
assert isinstance(as_string, bool)
if as_string:
return self._input_string
if self._color_type == COLORINPUT_TYPE_RGB:
color = self._input_string.split(self._separator)
if len(color) == 3 and color[0] != '' and color[1] != '' and color[2] != '':
r, g, b = int(color[0]), int(color[1]), int(color[2])
if 0 <= r <= 255 and 0 <= g <= 255 and 0 <= g <= 255:
return r, g, b
elif self._color_type == COLORINPUT_TYPE_HEX:
if len(self._input_string) == 7:
color = self._input_string[1:]
color = tuple(int(color[i:i + 2], 16) for i in (0, 2, 4))
return color[0], color[1], color[2]
return -1, -1, -1
[docs] def is_valid(self) -> bool:
"""
Return ``True`` if the current value of the input is a valid color or not.
:return: ``True`` if valid
"""
r, g, b = self.get_value()
if r == -1 or g == -1 or b == -1:
return False
return True
def _draw(self, surface: 'pygame.Surface') -> None:
super(ColorInput, self)._draw(surface) # This calls _render()
# Draw previsualization box
if self._previsualization_surface is not None:
posx = self._rect.x + self._rect.width \
- self._prev_width_factor * self._rect.height \
+ self._rect.height / 10
posy = self._rect.y
surface.blit(self._previsualization_surface, (int(posx), int(posy)))
def _render(self) -> Optional[bool]:
render_text = super(ColorInput, self)._render()
# Maybe TextInput did not render, so this has to be changed
self._rect.width, self._rect.height = self._surface.get_size()
if not self._dynamic_width or \
(self._dynamic_width and self._previsualization_surface is not None):
self._rect.width += self._prev_width_factor * self._rect.height \
+ self._prev_margin
# Render the previsualization box
r, g, b = self.get_value()
if not self.is_valid(): # Remove previsualization if invalid color
self._previsualization_surface = None
return render_text
# If previsualization surface is None or the color changed
if self._last_r != r or self._last_b != b or self._last_g != g or \
self._previsualization_surface is None:
width = self._prev_width_factor * self._rect.height
if width == 0 or self._rect.height == 0:
self._previsualization_surface = None
else:
self._previsualization_surface = make_surface(width, self._rect.height)
self._previsualization_surface.fill((r, g, b))
self._last_r = r
self._last_g = g
self._last_b = b
if self._dynamic_width:
self._rect.width += self._prev_width_factor * self._rect.height \
+ self._prev_margin
return render_text
def _format_hex(self) -> None:
"""
Apply hex format.
"""
if self._color_type != COLORINPUT_TYPE_HEX or \
self._hex_format == COLORINPUT_HEX_FORMAT_NONE:
return
elif self._hex_format == COLORINPUT_HEX_FORMAT_LOWER:
self._input_string = self._input_string.lower()
elif self._hex_format == COLORINPUT_HEX_FORMAT_UPPER:
self._input_string = self._input_string.upper()
[docs] def update(self, events: EventVectorType) -> bool:
self.apply_update_callbacks(events)
if self.readonly or not self.is_visible():
self._readonly_check_mouseover(events)
return False
input_str = self._input_string
cursor_pos = self._cursor_position
disable_remove_separator = True
key = '' # Pressed key
if self._color_type == COLORINPUT_TYPE_RGB:
for event in events:
# User writes
if event.type == pygame.KEYDOWN and self._keyboard_enabled:
# Check if any key is pressed
if self._ignores_keyboard_nonphysical() and not check_key_pressed_valid(event):
continue
if disable_remove_separator and len(input_str) > 0 and \
len(input_str) > cursor_pos and (
f'{self._separator}{self._separator}' not in input_str or
input_str[cursor_pos] == self._separator and
len(input_str) == cursor_pos + 1
):
# Backspace button, delete text from right
if event.key == pygame.K_BACKSPACE:
if len(input_str) >= 1 and \
input_str[cursor_pos - 1] == self._separator:
return True
# Delete button, delete text from left
elif event.key == pygame.K_DELETE:
if input_str[cursor_pos] == self._separator:
return True
# Verify only on user key input, the rest of events are checked
# by TextInput on super call
key = str(event.unicode)
if key in self._valid_chars:
new_string = (
self._input_string[:self._cursor_position]
+ key
+ self._input_string[self._cursor_position:]
)
# Cannot be separator at first
if len(input_str) == 0 and key == self._separator:
return False
if len(input_str) > 1:
# Check separators
if key == self._separator:
# If more than 2 separators
total_separator = 0
for ch in input_str:
if ch == self._separator:
total_separator += 1
if total_separator >= 2:
return False
# Check the number between the current separators,
# this number must be between 0-255
if key != self._separator:
pos_before = 0
pos_after = 0
for i in range(cursor_pos):
if new_string[cursor_pos - i - 1] == self._separator:
pos_before = cursor_pos - i
break
for i in range(len(new_string) - cursor_pos):
if new_string[cursor_pos + i] == self._separator:
pos_after = cursor_pos + i
break
if pos_after == 0:
pos_after = len(new_string)
num = new_string[pos_before:pos_after].replace(',', '')
if num == '':
num = '0'
if int(num) > 255: # Number exceeds 25X
return False
# User adds 0 at left, example: 12 -> 012
if num != str(int(num)) and key == '0':
return False
if len(num) > 3: # Number like 0XXX
return False
elif self._color_type == COLORINPUT_TYPE_HEX:
self._format_hex()
for event in events:
# User writes
if event.type == pygame.KEYDOWN and self._keyboard_enabled:
# Check if any key is pressed
if self._ignores_keyboard_nonphysical() and not check_key_pressed_valid(event):
continue
# Backspace button, delete text from right
if event.key == pygame.K_BACKSPACE:
if cursor_pos == 1:
return True
# Delete button, delete text from left
elif event.key == pygame.K_DELETE:
if cursor_pos == 0:
return True
# Verify only on user key input, the rest of events are checked
# by TextInput on super call
key = str(event.unicode)
if key in self._valid_chars:
if key == '#':
return True
if cursor_pos == 0:
return True
# Update
updated = super(ColorInput, self).update(events)
# After
if self._color_type == COLORINPUT_TYPE_RGB:
total_separator = 0
for ch in input_str:
if ch == self._separator:
total_separator += 1
# Adds auto separator
if key == '0' and len(self._input_string) == self._cursor_position and \
total_separator < 2 and \
(len(self._input_string) == 1 or
(len(self._input_string) > 2 and self._input_string[
self._cursor_position - 2] == self._separator)):
self._push_key_input(self._separator, sounds=False) # This calls .onchange()
# Check number is valid (fix) because sometimes the user can type
# too fast and avoid analysis of the text
colors = self._input_string.split(self._separator)
for c in colors:
if len(c) > 0 and (int(c) > 255 or int(c) < 0):
self._input_string = input_str
self._cursor_position = cursor_pos
break
if len(colors) == 3:
self._auto_separator_pos = [0, 1]
# Add an auto separator if the number can't continue growing and the cursor
# is at the end of the line
if total_separator < 2 and len(self._input_string) == self._cursor_position:
auto_pos = len(colors) - 1
last_num = colors[auto_pos]
if (len(last_num) == 2 and int(last_num) > 25 or len(last_num) == 3 and
int(last_num) <= 255) and \
auto_pos not in self._auto_separator_pos:
self._push_key_input(self._separator, sounds=False) # This calls .onchange()
self._auto_separator_pos.append(auto_pos)
# If the user cleared all the string, reset auto separator
if total_separator == 0 and \
(len(self._input_string) < 2 or len(self._input_string) == 2 and
int(colors[0]) <= 25):
self._auto_separator_pos = []
return updated
class ColorInputManager(AbstractWidgetManager, ABC):
"""
ColorInput manager.
"""
def color_input(
self,
title: Union[str, Any],
color_type: ColorInputColorType,
color_id: str = '',
default: Union[str, Tuple3IntType] = '',
hex_format: ColorInputHexFormatType = COLORINPUT_HEX_FORMAT_NONE,
input_separator: str = ',',
input_underline: str = '_',
onchange: CallbackType = None,
onreturn: CallbackType = None,
onselect: Optional[Callable[[bool, 'Widget', 'pygame_menu.Menu'], Any]] = None,
**kwargs
) -> 'pygame_menu.widgets.ColorInput':
"""
Add a color widget with RGB or HEX format to the Menu.
Includes a preview box that renders the given color.
The callbacks (if defined) receive the current value and all unknown
keyword arguments, where ``current_color=widget.get_value()``:
.. code-block:: python
onchange(current_color, **kwargs)
onreturn(current_color, **kwargs)
If ``onselect`` is defined, the callback is executed as follows, where
``selected`` is a boolean representing the selected status:
.. code-block:: python
onselect(selected, widget, menu)
kwargs (Optional)
- ``align`` (str) – Widget `alignment <https://pygame-menu.readthedocs.io/en/latest/_source/themes.html#alignment>`_
- ``background_color`` (tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage`) – Color of the background. ``None`` for no-color
- ``background_inflate`` (tuple, list) – Inflate background on x-axis and y-axis (x, y) in px
- ``border_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Widget border color. ``None`` for no-color
- ``border_inflate`` (tuple, list) – Widget border inflate on x-axis and y-axis (x, y) in px
- ``border_position`` (str, tuple, list) – Widget border positioning. It can be a single position, or a tuple/list of positions. Only are accepted: north, south, east, and west. See :py:mod:`pygame_menu.locals`
- ``border_width`` (int) – Border width in px. If ``0`` disables the border
- ``cursor`` (int, :py:class:`pygame.cursors.Cursor`, None) – Cursor of the widget if the mouse is placed over
- ``dynamic_width`` (bool) – If ``True`` the widget width changes if the pre-visualization color box is active or not
- ``float`` (bool) - If ``True`` the widget don't contribute width/height to the Menu widget positioning computation, and don't add one unit to the rows
- ``float_origin_position`` (bool) - If ``True`` the widget position is set to the top-left position of the Menu if the widget is floating
- ``font_background_color`` (tuple, list, str, int, :py:class:`pygame.Color`, None) – Widget font background color
- ``font_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Widget font color
- ``font_name`` (str, :py:class:`pathlib.Path`, :py:class:`pygame.font.Font`) – Widget font path
- ``font_shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Font shadow color
- ``font_shadow_offset`` (int) – Font shadow offset in px
- ``font_shadow_position`` (str) – Font shadow position, see locals for position
- ``font_shadow`` (bool) – Font shadow is enabled or disabled
- ``font_size`` (int) – Font size of the widget
- ``input_underline_vmargin`` (int) – Vertical margin of underline in px
- ``margin`` (tuple, list) – Widget (left, bottom) margin in px
- ``maxwidth_dynamically_update`` (bool) - Dynamically update maxwidth depending on char size. ``True`` by default
- ``padding`` (int, float, tuple, list) – Widget padding according to CSS rules. General shape: (top, right, bottom, left)
- ``previsualization_margin`` (int) – Pre-visualization left margin from text input in px. Default is ``0``
- ``previsualization_width`` (int, float) – Pre-visualization width as a factor of the height. Default is ``3``
- ``readonly_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode
- ``readonly_selected_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode and is selected
- ``repeat_keys_initial_ms`` (int, float) - Time in ms before keys are repeated when held in ms. ``400`` by default
- ``repeat_keys_interval_ms`` (int, float) - Interval between key press repetition when held in ms. ``50`` by default
- ``repeat_mouse_interval_ms`` (int, float) - Interval between mouse events when held in ms. ``400`` by default
- ``selection_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the selected widget; only affects the font color
- ``selection_effect`` (:py:class:`pygame_menu.widgets.core.Selection`) – Widget selection effect
- ``shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget shadow
- ``shadow_radius`` (int) - Border radius of the shadow
- ``shadow_type`` (str) - Shadow type, it can be ``'rectangular'`` or ``'ellipse'``
- ``shadow_width`` (int) - Width of the shadow. If ``0`` the shadow is disabled
- ``tab_size`` (int) – Width of a tab character
.. note::
All theme-related optional kwargs use the default Menu theme if not
defined.
.. note::
This is applied only to the base Menu (not the currently displayed,
stored in ``_current`` pointer); for such behaviour apply to
:py:meth:`pygame_menu.menu.Menu.get_current` object.
.. warning::
Be careful with kwargs collision. Consider that all optional documented
kwargs keys are removed from the object.
:param title: Title of the color input
:param color_type: Type of the color input
:param color_id: ID of the color input
:param default: Default value to display, if RGB type it must be a tuple ``(r, g, b)``, if HEX must be a string ``"#XXXXXX"``
:param hex_format: Hex format string mode
:param input_separator: Divisor between RGB channels, not valid in HEX format
:param input_underline: Underline character
:param onchange: Callback executed when changing the values of the color text
:param onreturn: Callback executed when pressing return on the color input
:param onselect: Callback executed when selecting the widget
:param kwargs: Optional keyword arguments
:return: Widget object
:rtype: :py:class:`pygame_menu.widgets.ColorInput`
"""
assert isinstance(default, (str, tuple))
# Filter widget attributes to avoid passing them to the callbacks
attributes = self._filter_widget_attributes(kwargs)
dynamic_width = kwargs.pop('dynamic_width', True)
input_underline_vmargin = kwargs.pop('input_underline_vmargin', 0)
prev_margin = kwargs.pop('previsualization_margin', 10)
prev_width = kwargs.pop('previsualization_width', 3)
widget = ColorInput(
color_type=color_type,
colorinput_id=color_id,
cursor_color=self._theme.cursor_color,
cursor_switch_ms=self._theme.cursor_switch_ms,
dynamic_width=dynamic_width,
hex_format=hex_format,
input_separator=input_separator,
input_underline=input_underline,
input_underline_vmargin=input_underline_vmargin,
onchange=onchange,
onreturn=onreturn,
onselect=onselect,
prev_margin=prev_margin,
prev_width_factor=prev_width,
title=title,
**kwargs
)
self._configure_widget(widget=widget, **attributes)
self._append_widget(widget)
widget.set_default_value(default)
return widget