Source code for pygame_menu.widgets.widget.colorinput

# coding=utf-8
"""
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.

License:
-------------------------------------------------------------------------------
The MIT License (MIT)
Copyright 2017-2020 Pablo Pizarro R. @ppizarror

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
"""

import pygame
import pygame_menu.locals as _locals
from pygame_menu.utils import check_key_pressed_valid, make_surface
from pygame_menu.widgets.widget.textinput import TextInput

TYPE_HEX = 'hex'
TYPE_RGB = 'rgb'


[docs]class ColorInput(TextInput): """ Color input widget. :param label: Input label text :type label: str :param colorinput_id: ID of the text input :type colorinput_id: str :param color_type: Type of color input (rgb, hex) :type color_type: str :param input_separator: Divisor between RGB channels :type input_separator: str :param input_underline: Character drawn under each number input :type input_underline: str :param cursor_color: Color of cursor :type cursor_color: tuple :param onchange: Callback when changing the selector :type onchange: callable, None :param onreturn: Callback when pressing return button :type onreturn: callable, None :param prev_size: Width of the previsualization box in terms of the height of the widget :type prev_size: int, float :param repeat_keys_initial_ms: Time in ms before keys are repeated when held :type repeat_keys_initial_ms: int, float :param repeat_keys_interval_ms: Interval between key press repetition when held :type repeat_keys_interval_ms: int, float :param repeat_mouse_interval_ms: Interval between mouse events when held :type repeat_mouse_interval_ms: int, float :param kwargs: Optional keyword-arguments for callbacks :type kwargs: any """ def __init__(self, label='', colorinput_id='', color_type=TYPE_RGB, input_separator=',', input_underline='_', cursor_color=(0, 0, 0), onchange=None, onreturn=None, prev_size=3, repeat_keys_initial_ms=450, repeat_keys_interval_ms=80, repeat_mouse_interval_ms=100, *args, **kwargs): assert isinstance(label, str) assert isinstance(colorinput_id, str) assert isinstance(color_type, str) assert isinstance(input_separator, str) assert isinstance(input_underline, str) assert isinstance(cursor_color, tuple) assert isinstance(repeat_keys_initial_ms, (int, float)) assert isinstance(repeat_keys_interval_ms, (int, float)) assert isinstance(repeat_mouse_interval_ms, (int, float)) assert isinstance(prev_size, (int, float)) assert len(input_separator) == 1, 'input_separator must be a single char' assert len(input_separator) != 0, 'input_separator cannot be empty' assert prev_size > 0, 'previsualization width 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 [TYPE_HEX, TYPE_RGB], 'color type must be "{0}" or "{1}"'.format(TYPE_HEX, TYPE_RGB) _maxchar = 0 self._color_type = color_type.lower() # type: str if self._color_type == 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 == 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'] _input_type = _locals.INPUT_TEXT _maxwidth = 0 _password = False super(ColorInput, self).__init__( label=label, textinput_id=colorinput_id, input_type=_input_type, input_underline=input_underline, cursor_color=cursor_color, copy_paste_enable=False, cursor_selection_enable=False, history=0, maxchar=_maxchar, maxwidth=_maxwidth, # Disabled onchange=onchange, onreturn=onreturn, password=_password, repeat_keys_initial_ms=repeat_keys_initial_ms, repeat_keys_interval_ms=repeat_keys_interval_ms, repeat_mouse_interval_ms=repeat_mouse_interval_ms, tab_size=0, text_ellipsis='', valid_chars=self._valid_chars, *args, **kwargs ) # Store inner variables self._separator = input_separator # Previsualization surface, if -1 previsualization does not show self._last_r = -1 # type: int self._last_g = -1 # type: int self._last_b = -1 # type: int self._previsualization_position = (0.0, 0.0) self._previsualization_surface = None # type: (pygame.Surface,None) self._prev_size = prev_size # type: int # noinspection PyMissingOrEmptyDocstring
[docs] def clear(self): super(ColorInput, self).clear() self._previsualization_surface = None if self._color_type == TYPE_HEX: super(ColorInput, self).set_value('#') self.change()
# noinspection PyMissingOrEmptyDocstring
[docs] def set_value(self, rgb_tuple): _color = '' if self._color_type == TYPE_RGB: if rgb_tuple == '': super(ColorInput, self).set_value('') return assert isinstance(rgb_tuple, tuple), 'Color in rgb format must be a tuple in (r,g,b) format' assert len(rgb_tuple) == 3, 'Tuple must contain only 3 colors, R,G,B' r, g, b = rgb_tuple 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' _color = '{0}{3}{1}{3}{2}'.format(r, g, b, self._separator) elif self._color_type == TYPE_HEX: text = str(rgb_tuple).strip() if text == '': _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, 'Color invalid, only formats "#RRGGBB" and "RRGGBB" are allowed' _color = text super(ColorInput, self).set_value(_color)
[docs] def get_value(self): """ Return the color value as a tuple or red blue and green channels. If the data is invalid the widget returns (-1,-1,-1). :return: Color tuple as (R,G,B) :rtype: tuple """ if self._color_type == 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 == TYPE_HEX: if len(self._input_string) == 7: _color = self._input_string[1:] return tuple(int(_color[i:i + 2], 16) for i in (0, 2, 4)) return -1, -1, -1
def _previsualize_color(self, surface): """ Changes the color of the previsualization box. :param surface: Surface to draw :type surface: :py:class:`pygame.surface.Surface`, None :return: None """ r, g, b = self.get_value() if r == -1 or g == -1 or b == -1: # Remove previsualization if invalid color self._previsualization_surface = None return # 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_size * self._rect.height if _width == 0 or self._rect.height == 0: self._previsualization_surface = None return 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 _posx = self._rect.x + self._rect.width - self._prev_size * self._rect.height + self._rect.height / 10 _posy = self._rect.y self._previsualization_position = (_posx, _posy) # Draw the surface if surface is not None: surface.blit(self._previsualization_surface, self._previsualization_position) # noinspection PyMissingOrEmptyDocstring
[docs] def draw(self, surface): super(ColorInput, self).draw(surface) # This calls _render() self._previsualize_color(surface)
def _render(self): super(ColorInput, self)._render() # Maybe TextInput did not rendered, so this has to be changed self._rect.width, self._rect.height = self._surface.get_size() self._rect.width += self._prev_size * self._rect.height # Adds the previsualization size to the box # noinspection PyMissingOrEmptyDocstring
[docs] def update(self, events): _input = self._input_string _curpos = self._cursor_position _disable_remove_separator = True key = '' # Pressed key if self._color_type == TYPE_RGB: for event in events: # type: pygame.event.Event if event.type == pygame.KEYDOWN: # Check if any key is pressed, if True the event is invalid if not check_key_pressed_valid(event): return True if _disable_remove_separator and len(_input) > 0 and len(_input) > _curpos and ( '{0}{0}'.format(self._separator) not in _input or _input[_curpos] == self._separator and len(_input) == _curpos + 1 ): # Backspace button, delete text from right if event.key == pygame.K_BACKSPACE: if len(_input) >= 1 and _input[_curpos - 1] == self._separator: return True # Delete button, delete text from left elif event.key == pygame.K_DELETE: if _input[_curpos] == 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) == 0 and key == self._separator: return False if len(_input) > 1: # Check separators if key == self._separator: # If more than 2 separators _total_separator = 0 for _ch in _input: 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(_curpos): if _new_string[_curpos - _i - 1] == self._separator: _pos_before = _curpos - _i break for _i in range(len(_new_string) - _curpos): if _new_string[_curpos + _i] == self._separator: _pos_after = _curpos + _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 if _num != str(int(_num)) and key == '0': # User adds 0 at left, example: 12 -> 012 return False if len(_num) > 3: # Number like 0XXX return False elif self._color_type == TYPE_HEX: for event in events: # type: pygame.event.Event if event.type == pygame.KEYDOWN: # Check if any key is pressed, if True the event is invalid if not check_key_pressed_valid(event): return True # Backspace button, delete text from right if event.key == pygame.K_BACKSPACE: if _curpos == 1: return True # Delete button, delete text from left elif event.key == pygame.K_DELETE: if _curpos == 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 _curpos == 0: return True # Update updated = super(ColorInput, self).update(events) # After if self._color_type == TYPE_RGB: _total_separator = 0 for _ch in _input: 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 self._cursor_position = _curpos break return updated