pytermgui.widgets.color_picker
The module containing the ColorPicker widget, as well as some helpers it needs.
To test out the widget, run ptg --color
!
View Source
0"""The module containing the ColorPicker widget, as well as some helpers it needs. 1 2To test out the widget, run `ptg --color`! 3""" 4 5from __future__ import annotations 6 7from typing import Any 8from contextlib import suppress 9 10from . import boxes 11from ..regex import real_length 12from .base import Label, Widget 13from .interactive import Button 14from ..colors import str_to_color 15from .containers import Container 16from ..animations import animator 17from .pixel_matrix import PixelMatrix 18from ..enums import SizePolicy, HorizontalAlignment 19from ..ansi_interface import MouseAction, MouseEvent 20 21 22def _get_xterm_matrix() -> list[list[str]]: 23 """Creates a matrix containing all 255 xterm-255 colors. 24 25 The top row contains the normal & bright colors, with some 26 space in between. 27 28 The second row contains all shades of black. 29 30 Finally, the third section is a table of all remaining colors. 31 """ 32 33 matrix: list[list[str]] = [] 34 for _ in range(11): 35 current_row = [] 36 for _ in range(36): 37 current_row.append("") 38 matrix.append(current_row) 39 40 offset = 0 41 for color in range(16): 42 if color == 8: 43 offset += 4 44 45 cursor = offset 46 for _ in range(2): 47 matrix[0][cursor] = str(color) 48 cursor += 1 49 50 offset = cursor 51 52 offset = 7 53 for color in range(23): 54 cursor = offset 55 56 matrix[2][cursor] = str(232 + color) 57 matrix[3][cursor] = str(min(232 + color + 1, 255)) 58 cursor += 1 59 60 offset = cursor 61 62 cursor = 16 63 for row in range(5, 11): 64 for column in range(37): 65 if column == 36: 66 continue 67 68 matrix[row][column] = str(cursor + column) 69 70 cursor += column 71 72 if cursor > 232: 73 break 74 75 return matrix 76 77 78class Joiner(Container): 79 """A Container that stacks widgets horizontally, without filling up the available space. 80 81 This works slightly differently to Splitter, as that applies padding & custom widths to 82 any Widget it finds. This works much more simply, and only joins their lines together as 83 they come. 84 """ 85 86 parent_align = HorizontalAlignment.LEFT 87 88 chars = {"separator": " "} 89 90 def get_lines(self) -> list[str]: 91 """Does magic""" 92 93 lines: list[str] = [] 94 separator = self._get_char("separator") 95 assert isinstance(separator, str) 96 97 line = "" 98 for widget in self._widgets: 99 if len(line) > 0: 100 line += separator 101 102 widget.pos = (self.pos[0] + real_length(line), self.pos[1] + len(lines)) 103 widget_line = widget.get_lines()[0] 104 105 if real_length(line + widget_line) >= self.width: 106 lines.append(line) 107 widget.pos = self.pos[0], self.pos[1] + len(lines) 108 line = widget_line 109 continue 110 111 line += widget_line 112 113 lines.append(line) 114 self.height = len(lines) 115 return lines 116 117 118class _FadeInButton(Button): 119 """A Button with a fade-in animation.""" 120 121 def __init__(self, *args: Any, **attrs: Any) -> None: 122 """Initialize _FadeInButton. 123 124 As this is nothing more than an extension on top of 125 `pytermgui.widgets.interactive.Button`, check that documentation 126 for more information. 127 """ 128 129 super().__init__(*args, **attrs) 130 self.onclick = self.remove_from_parent 131 self.set_char("delimiter", ["", ""]) 132 133 self._fade_progress = 0 134 135 self.get_lines() 136 137 # TODO: Why is that +2 needed? 138 animator.animate_attr( 139 target=self, 140 attr="_fade_progress", 141 start=0, 142 end=self.width + 2, 143 duration=150, 144 ) 145 146 def remove_from_parent(self, _: Widget) -> None: 147 """Removes self from parent, when possible.""" 148 149 def _on_finish(_: object) -> None: 150 """Removes button on animation finish.""" 151 152 assert isinstance(self.parent, Container) 153 154 with suppress(ValueError): 155 self.parent.remove(self) 156 157 animator.animate_attr( 158 target=self, 159 attr="_fade_progress", 160 start=self.width, 161 end=0, 162 duration=150, 163 on_finish=_on_finish, 164 ) 165 166 def get_lines(self) -> list[str]: 167 """Gets the lines from Button, and cuts them off at self._fade_progress""" 168 169 return [self.styles.label(self.label[: self._fade_progress])] 170 171 172class ColorPicker(Container): 173 """A simple ColorPicker widget. 174 175 This is used to visualize xterm-255 colors. RGB colors are not 176 included here, as it is probably easier to use a web-based picker 177 for those anyways. 178 """ 179 180 size_policy = SizePolicy.STATIC 181 182 def __init__(self, show_output: bool = True, **attrs: Any) -> None: 183 """Initializes a ColorPicker. 184 185 Attrs: 186 show_output: Decides whether the output Container should be 187 added. If not set, the widget will only display the 188 PixelMatrix of colors. 189 """ 190 191 super().__init__(**attrs) 192 self.show_output = show_output 193 194 self._matrix = PixelMatrix.from_matrix(_get_xterm_matrix()) 195 196 self.width = 72 197 self.box = boxes.EMPTY 198 199 self._add_widget(self._matrix, run_get_lines=False) 200 201 self.chosen = Joiner() 202 self._output = Container(self.chosen, "", "", "") 203 204 if self.show_output: 205 self._add_widget(self._output) 206 207 def handle_mouse(self, event: MouseEvent) -> bool: 208 """Handles mouse events. 209 210 On hover, the widget will display the currently hovered 211 color and some testing text. 212 213 On click, it will add a _FadeInButton for the currently 214 hovered color. 215 216 Args: 217 event: The event to handle. 218 """ 219 220 if super().handle_mouse(event): 221 return True 222 223 if not self.show_output or not self._matrix.contains(event.position): 224 return False 225 226 if event.action is MouseAction.LEFT_CLICK: 227 if self._matrix.selected_pixel is None: 228 return True 229 230 _, color = self._matrix.selected_pixel 231 if len(color) == 0: 232 return False 233 234 button = _FadeInButton(f"{color:^5}", width=5) 235 button.styles.label = f"black @{color}" 236 self.chosen.lazy_add(button) 237 238 return True 239 240 return False 241 242 def get_lines(self) -> list[str]: 243 """Updates self._output and gets widget lines.""" 244 245 if self.show_output and self._matrix.selected_pixel is not None: 246 _, color = self._matrix.selected_pixel 247 if len(color) == 0: 248 return super().get_lines() 249 250 color_obj = str_to_color(color) 251 rgb = color_obj.rgb 252 hex_ = color_obj.hex 253 lines: list[Widget] = [ 254 Label(f"[black @{color}] {color} [/ {color}] {color}"), 255 Label( 256 f"[{color} bold]Here[/bold italic] is " 257 + "[/italic underline]some[/underline dim] example[/dim] text" 258 ), 259 Label(), 260 Label( 261 f"RGB: [{';'.join(map(str, rgb))}]" 262 + f"rgb({rgb[0]:>3}, {rgb[1]:>3}, {rgb[2]:>3})" 263 ), 264 Label(f"HEX: [{hex_}]{hex_}"), 265 ] 266 self._output.set_widgets(lines + [Label(), self.chosen]) 267 268 return super().get_lines() 269 270 return super().get_lines()
View Source
79class Joiner(Container): 80 """A Container that stacks widgets horizontally, without filling up the available space. 81 82 This works slightly differently to Splitter, as that applies padding & custom widths to 83 any Widget it finds. This works much more simply, and only joins their lines together as 84 they come. 85 """ 86 87 parent_align = HorizontalAlignment.LEFT 88 89 chars = {"separator": " "} 90 91 def get_lines(self) -> list[str]: 92 """Does magic""" 93 94 lines: list[str] = [] 95 separator = self._get_char("separator") 96 assert isinstance(separator, str) 97 98 line = "" 99 for widget in self._widgets: 100 if len(line) > 0: 101 line += separator 102 103 widget.pos = (self.pos[0] + real_length(line), self.pos[1] + len(lines)) 104 widget_line = widget.get_lines()[0] 105 106 if real_length(line + widget_line) >= self.width: 107 lines.append(line) 108 widget.pos = self.pos[0], self.pos[1] + len(lines) 109 line = widget_line 110 continue 111 112 line += widget_line 113 114 lines.append(line) 115 self.height = len(lines) 116 return lines
A Container that stacks widgets horizontally, without filling up the available space.
This works slightly differently to Splitter, as that applies padding & custom widths to any Widget it finds. This works much more simply, and only joins their lines together as they come.
pytermgui.enums.HorizontalAlignment
to align widget by
Default characters for this class
View Source
91 def get_lines(self) -> list[str]: 92 """Does magic""" 93 94 lines: list[str] = [] 95 separator = self._get_char("separator") 96 assert isinstance(separator, str) 97 98 line = "" 99 for widget in self._widgets: 100 if len(line) > 0: 101 line += separator 102 103 widget.pos = (self.pos[0] + real_length(line), self.pos[1] + len(lines)) 104 widget_line = widget.get_lines()[0] 105 106 if real_length(line + widget_line) >= self.width: 107 lines.append(line) 108 widget.pos = self.pos[0], self.pos[1] + len(lines) 109 line = widget_line 110 continue 111 112 line += widget_line 113 114 lines.append(line) 115 self.height = len(lines) 116 return lines
Does magic
Inherited Members
- pytermgui.widgets.containers.Container
- Container
- styles
- keys
- serialized
- vertical_align
- allow_fullscreen
- overflow
- sidelength
- content_dimensions
- selectables
- selectables_length
- selected
- box
- get_change
- lazy_add
- set_widgets
- serialize
- pop
- remove
- set_recursive_depth
- select
- center
- handle_mouse
- execute_binding
- handle_key
- wipe
- debug
View Source
173class ColorPicker(Container): 174 """A simple ColorPicker widget. 175 176 This is used to visualize xterm-255 colors. RGB colors are not 177 included here, as it is probably easier to use a web-based picker 178 for those anyways. 179 """ 180 181 size_policy = SizePolicy.STATIC 182 183 def __init__(self, show_output: bool = True, **attrs: Any) -> None: 184 """Initializes a ColorPicker. 185 186 Attrs: 187 show_output: Decides whether the output Container should be 188 added. If not set, the widget will only display the 189 PixelMatrix of colors. 190 """ 191 192 super().__init__(**attrs) 193 self.show_output = show_output 194 195 self._matrix = PixelMatrix.from_matrix(_get_xterm_matrix()) 196 197 self.width = 72 198 self.box = boxes.EMPTY 199 200 self._add_widget(self._matrix, run_get_lines=False) 201 202 self.chosen = Joiner() 203 self._output = Container(self.chosen, "", "", "") 204 205 if self.show_output: 206 self._add_widget(self._output) 207 208 def handle_mouse(self, event: MouseEvent) -> bool: 209 """Handles mouse events. 210 211 On hover, the widget will display the currently hovered 212 color and some testing text. 213 214 On click, it will add a _FadeInButton for the currently 215 hovered color. 216 217 Args: 218 event: The event to handle. 219 """ 220 221 if super().handle_mouse(event): 222 return True 223 224 if not self.show_output or not self._matrix.contains(event.position): 225 return False 226 227 if event.action is MouseAction.LEFT_CLICK: 228 if self._matrix.selected_pixel is None: 229 return True 230 231 _, color = self._matrix.selected_pixel 232 if len(color) == 0: 233 return False 234 235 button = _FadeInButton(f"{color:^5}", width=5) 236 button.styles.label = f"black @{color}" 237 self.chosen.lazy_add(button) 238 239 return True 240 241 return False 242 243 def get_lines(self) -> list[str]: 244 """Updates self._output and gets widget lines.""" 245 246 if self.show_output and self._matrix.selected_pixel is not None: 247 _, color = self._matrix.selected_pixel 248 if len(color) == 0: 249 return super().get_lines() 250 251 color_obj = str_to_color(color) 252 rgb = color_obj.rgb 253 hex_ = color_obj.hex 254 lines: list[Widget] = [ 255 Label(f"[black @{color}] {color} [/ {color}] {color}"), 256 Label( 257 f"[{color} bold]Here[/bold italic] is " 258 + "[/italic underline]some[/underline dim] example[/dim] text" 259 ), 260 Label(), 261 Label( 262 f"RGB: [{';'.join(map(str, rgb))}]" 263 + f"rgb({rgb[0]:>3}, {rgb[1]:>3}, {rgb[2]:>3})" 264 ), 265 Label(f"HEX: [{hex_}]{hex_}"), 266 ] 267 self._output.set_widgets(lines + [Label(), self.chosen]) 268 269 return super().get_lines() 270 271 return super().get_lines()
A simple ColorPicker widget.
This is used to visualize xterm-255 colors. RGB colors are not included here, as it is probably easier to use a web-based picker for those anyways.
View Source
183 def __init__(self, show_output: bool = True, **attrs: Any) -> None: 184 """Initializes a ColorPicker. 185 186 Attrs: 187 show_output: Decides whether the output Container should be 188 added. If not set, the widget will only display the 189 PixelMatrix of colors. 190 """ 191 192 super().__init__(**attrs) 193 self.show_output = show_output 194 195 self._matrix = PixelMatrix.from_matrix(_get_xterm_matrix()) 196 197 self.width = 72 198 self.box = boxes.EMPTY 199 200 self._add_widget(self._matrix, run_get_lines=False) 201 202 self.chosen = Joiner() 203 self._output = Container(self.chosen, "", "", "") 204 205 if self.show_output: 206 self._add_widget(self._output)
Initializes a ColorPicker.
Attrs
show_output: Decides whether the output Container should be added. If not set, the widget will only display the PixelMatrix of colors.
pytermgui.enums.SizePolicy
to set widget's width according to
View Source
208 def handle_mouse(self, event: MouseEvent) -> bool: 209 """Handles mouse events. 210 211 On hover, the widget will display the currently hovered 212 color and some testing text. 213 214 On click, it will add a _FadeInButton for the currently 215 hovered color. 216 217 Args: 218 event: The event to handle. 219 """ 220 221 if super().handle_mouse(event): 222 return True 223 224 if not self.show_output or not self._matrix.contains(event.position): 225 return False 226 227 if event.action is MouseAction.LEFT_CLICK: 228 if self._matrix.selected_pixel is None: 229 return True 230 231 _, color = self._matrix.selected_pixel 232 if len(color) == 0: 233 return False 234 235 button = _FadeInButton(f"{color:^5}", width=5) 236 button.styles.label = f"black @{color}" 237 self.chosen.lazy_add(button) 238 239 return True 240 241 return False
Handles mouse events.
On hover, the widget will display the currently hovered color and some testing text.
On click, it will add a _FadeInButton for the currently hovered color.
Args
- event: The event to handle.
View Source
243 def get_lines(self) -> list[str]: 244 """Updates self._output and gets widget lines.""" 245 246 if self.show_output and self._matrix.selected_pixel is not None: 247 _, color = self._matrix.selected_pixel 248 if len(color) == 0: 249 return super().get_lines() 250 251 color_obj = str_to_color(color) 252 rgb = color_obj.rgb 253 hex_ = color_obj.hex 254 lines: list[Widget] = [ 255 Label(f"[black @{color}] {color} [/ {color}] {color}"), 256 Label( 257 f"[{color} bold]Here[/bold italic] is " 258 + "[/italic underline]some[/underline dim] example[/dim] text" 259 ), 260 Label(), 261 Label( 262 f"RGB: [{';'.join(map(str, rgb))}]" 263 + f"rgb({rgb[0]:>3}, {rgb[1]:>3}, {rgb[2]:>3})" 264 ), 265 Label(f"HEX: [{hex_}]{hex_}"), 266 ] 267 self._output.set_widgets(lines + [Label(), self.chosen]) 268 269 return super().get_lines() 270 271 return super().get_lines()
Updates self._output and gets widget lines.
Inherited Members
- pytermgui.widgets.containers.Container
- box
- styles
- chars
- keys
- serialized
- vertical_align
- allow_fullscreen
- overflow
- sidelength
- content_dimensions
- selectables
- selectables_length
- selected
- get_change
- lazy_add
- set_widgets
- serialize
- pop
- remove
- set_recursive_depth
- select
- center
- execute_binding
- handle_key
- wipe
- debug