pytermgui.window_manager.window
The Window class, which is an implementation of pytermgui.widgets.Container
that
allows for mouse-based moving and resizing.
1"""The Window class, which is an implementation of `pytermgui.widgets.Container` that 2allows for mouse-based moving and resizing.""" 3 4from __future__ import annotations 5 6from typing import TYPE_CHECKING, Any 7 8from ..ansi_interface import MouseAction, MouseEvent 9from ..enums import CenteringPolicy, Overflow, SizePolicy 10from ..widgets import Container, Widget 11from ..widgets import styles as w_styles 12 13if TYPE_CHECKING: 14 from .manager import WindowManager 15 16 17class Window(Container): # pylint: disable=too-many-instance-attributes 18 """A class representing a window. 19 20 Windows are essentially fancy `pytermgui.widgets.Container`-s. They build on top of them 21 to store and display various widgets, while allowing some custom functionality. 22 """ 23 24 is_bindable = True 25 overflow = Overflow.HIDE 26 27 title = "" 28 """Title shown in left-top corner.""" 29 30 is_static = False 31 """Static windows cannot be moved using the mouse.""" 32 33 is_modal = False 34 """Modal windows stay on top of every other window and block interactions with other windows.""" 35 36 is_noblur = False 37 """No-blur windows will always appear to stay in focus, even if they functionally don't.""" 38 39 is_noresize = False 40 """No-resize windows cannot be resized using the mouse.""" 41 42 is_dirty = False 43 """Controls whether the window should be redrawn in the next frame.""" 44 45 is_persistent = False 46 """Persistent windows will be set noblur automatically, and remain clickable even through 47 modals. 48 49 While the library core doesn't do this for various reasons, it also might be useful to disable 50 some behaviour (e.g. closing) for persistent windows on an implementation level. 51 """ 52 53 chars = Container.chars.copy() 54 55 styles = w_styles.StyleManager( 56 border=w_styles.FOREGROUND, 57 corner=w_styles.FOREGROUND, 58 fill="", 59 border_focused=w_styles.FOREGROUND, 60 corner_focused=w_styles.FOREGROUND, 61 border_blurred="238", 62 corner_blurred="238", 63 ) 64 65 def __init__(self, *widgets: Any, **attrs: Any) -> None: 66 """Initializes object. 67 68 Args: 69 widgets: Widgets to add to this window after initilization. 70 attrs: Attributes that are passed to the constructor. 71 """ 72 73 self._min_width: int | None = None 74 self._auto_min_width: int | None = None 75 76 self.styles.border_focused = type(self).styles.border 77 self.styles.corner_focused = type(self).styles.corner 78 79 super().__init__(*widgets, **attrs) 80 81 self.has_focus: bool = False 82 83 self.manager: "WindowManager" | None = None 84 85 # ------------------------- position ----- width x height 86 self._restore_data: tuple[tuple[int, int], tuple[int, int]] | None = None 87 88 if self.title != "": 89 self.set_title(self.title) 90 91 if self.is_persistent: 92 self.is_noblur = True 93 94 @property 95 def min_width(self) -> int | None: 96 """Minimum width of the window. 97 98 If set to none, _auto_min_width will be calculated based on the maximum width of 99 inner widgets. 100 101 This is accurate enough for general use, but tends to lean to the safer side, 102 i.e. it often overshoots the 'real' minimum width possible. 103 104 If you find this to be the case, **AND** you can ensure that your window will 105 not break, you may set this value manually. 106 107 Returns: 108 The calculated, or given minimum width of this object. 109 """ 110 111 return self._min_width or self._auto_min_width 112 113 @min_width.setter 114 def min_width(self, new: int | None) -> None: 115 """Sets a new minimum width.""" 116 117 self._min_width = new 118 119 @property 120 def rect(self) -> tuple[int, int, int, int]: 121 """Returns the tuple of positions that define this window. 122 123 Returns: 124 A tuple of integers, in the order (left, top, right, bottom). 125 """ 126 127 left, top = self.pos 128 return (left, top, left + self.width, top + self.height) 129 130 @rect.setter 131 def rect(self, new: tuple[int, int, int, int]) -> None: 132 """Sets new position, width and height of this window. 133 134 This method also checks for the minimum width this window can be, and 135 if the new width doesn't comply with that setting the changes are thrown 136 away. 137 138 Args: 139 new: A tuple of integers in the order (left, top, right, bottom). 140 """ 141 142 left, top, right, bottom = new 143 minimum = self.min_width or 0 144 145 if right - left < minimum: 146 return 147 148 # Update size policy to fill to resize inner objects properly 149 self.size_policy = SizePolicy.FILL 150 self.pos = (left, top) 151 self.width = right - left 152 self.height = bottom - top 153 154 # Restore original size policy 155 self.size_policy = SizePolicy.STATIC 156 157 def __iadd__(self, other: object) -> Window: 158 """Calls self._add_widget(other) and returns self.""" 159 160 self._add_widget(other) 161 return self 162 163 def __add__(self, other: object) -> Window: 164 """Calls self._add_widget(other) and returns self.""" 165 166 self._add_widget(other) 167 return self 168 169 def _add_widget(self, other: object, run_get_lines: bool = True) -> Widget: 170 """Adds a widget to the window. 171 172 Args: 173 other: The widget-like to add. 174 run_get_lines: Whether self.get_lines should be ran after adding. 175 """ 176 177 added = super()._add_widget(other, run_get_lines) 178 179 if len(self._widgets) > 0: 180 self._auto_min_width = max(widget.width for widget in self._widgets) 181 self._auto_min_width += self.sidelength 182 183 self.height += added.height 184 185 return added 186 187 @classmethod 188 def set_focus_styles( 189 cls, 190 *, 191 focused: tuple[w_styles.StyleValue, w_styles.StyleValue], 192 blurred: tuple[w_styles.StyleValue, w_styles.StyleValue], 193 ) -> None: 194 """Sets focused & blurred border & corner styles. 195 196 Args: 197 focused: A tuple of border_focused, corner_focused styles. 198 blurred: A tuple of border_blurred, corner_blurred styles. 199 """ 200 201 cls.styles.border_focused, cls.styles.corner_focused = focused 202 cls.styles.border_blurred, cls.styles.corner_blurred = blurred 203 204 def focus(self) -> None: 205 """Focuses this window.""" 206 207 self.has_focus = True 208 209 if not self.is_noblur: 210 self.styles.border = self.styles.border_focused 211 self.styles.corner = self.styles.corner_focused 212 213 def blur(self) -> None: 214 """Blurs (unfocuses) this window.""" 215 216 self.has_focus = False 217 self.select(None) 218 self.handle_mouse(MouseEvent(MouseAction.RELEASE, (0, 0))) 219 220 if not self.is_noblur: 221 self.styles.border = self.styles.border_blurred 222 self.styles.corner = self.styles.corner_blurred 223 224 def clear_cache(self) -> None: 225 """Clears manager compositor's cached blur state.""" 226 227 if self.manager is not None: 228 self.manager.clear_cache(self) 229 230 def contains(self, pos: tuple[int, int]) -> bool: 231 """Determines whether widget contains `pos`. 232 233 This method uses window.rect to get the positions. 234 235 Args: 236 pos: Position to compare. 237 238 Returns: 239 Boolean describing whether the position is inside 240 this widget. 241 """ 242 243 left, top, right, bottom = self.rect 244 245 return left <= pos[0] < right and top <= pos[1] < bottom 246 247 def set_title(self, title: str, position: int = 0, pad: bool = True) -> Window: 248 """Sets the window's title. 249 250 Args: 251 title: The string to set as the window title. 252 position: An integer indexing into ["left", "top", "right", "bottom"], 253 determining where the title is applied. 254 pad: Whether there should be an extra space before and after the given title. 255 defaults to True. 256 """ 257 258 self.title = title 259 260 if pad: 261 title = " " + title + " " 262 263 corners = self._get_char("corner") 264 assert isinstance(corners, list) 265 266 if position % 2 == 0: 267 corners[position] += title 268 269 else: 270 current = corners[position] 271 corners[position] = title + current 272 273 self.set_char("corner", corners) 274 275 return self 276 277 def center( 278 self, where: CenteringPolicy | None = None, store: bool = True 279 ) -> Window: 280 """Center window""" 281 282 super().center(where, store) 283 return self 284 285 def close(self, animate: bool = True) -> None: 286 """Instruct window manager to close object""" 287 288 assert self.manager is not None 289 290 self.manager.remove(self, animate=animate)
18class Window(Container): # pylint: disable=too-many-instance-attributes 19 """A class representing a window. 20 21 Windows are essentially fancy `pytermgui.widgets.Container`-s. They build on top of them 22 to store and display various widgets, while allowing some custom functionality. 23 """ 24 25 is_bindable = True 26 overflow = Overflow.HIDE 27 28 title = "" 29 """Title shown in left-top corner.""" 30 31 is_static = False 32 """Static windows cannot be moved using the mouse.""" 33 34 is_modal = False 35 """Modal windows stay on top of every other window and block interactions with other windows.""" 36 37 is_noblur = False 38 """No-blur windows will always appear to stay in focus, even if they functionally don't.""" 39 40 is_noresize = False 41 """No-resize windows cannot be resized using the mouse.""" 42 43 is_dirty = False 44 """Controls whether the window should be redrawn in the next frame.""" 45 46 is_persistent = False 47 """Persistent windows will be set noblur automatically, and remain clickable even through 48 modals. 49 50 While the library core doesn't do this for various reasons, it also might be useful to disable 51 some behaviour (e.g. closing) for persistent windows on an implementation level. 52 """ 53 54 chars = Container.chars.copy() 55 56 styles = w_styles.StyleManager( 57 border=w_styles.FOREGROUND, 58 corner=w_styles.FOREGROUND, 59 fill="", 60 border_focused=w_styles.FOREGROUND, 61 corner_focused=w_styles.FOREGROUND, 62 border_blurred="238", 63 corner_blurred="238", 64 ) 65 66 def __init__(self, *widgets: Any, **attrs: Any) -> None: 67 """Initializes object. 68 69 Args: 70 widgets: Widgets to add to this window after initilization. 71 attrs: Attributes that are passed to the constructor. 72 """ 73 74 self._min_width: int | None = None 75 self._auto_min_width: int | None = None 76 77 self.styles.border_focused = type(self).styles.border 78 self.styles.corner_focused = type(self).styles.corner 79 80 super().__init__(*widgets, **attrs) 81 82 self.has_focus: bool = False 83 84 self.manager: "WindowManager" | None = None 85 86 # ------------------------- position ----- width x height 87 self._restore_data: tuple[tuple[int, int], tuple[int, int]] | None = None 88 89 if self.title != "": 90 self.set_title(self.title) 91 92 if self.is_persistent: 93 self.is_noblur = True 94 95 @property 96 def min_width(self) -> int | None: 97 """Minimum width of the window. 98 99 If set to none, _auto_min_width will be calculated based on the maximum width of 100 inner widgets. 101 102 This is accurate enough for general use, but tends to lean to the safer side, 103 i.e. it often overshoots the 'real' minimum width possible. 104 105 If you find this to be the case, **AND** you can ensure that your window will 106 not break, you may set this value manually. 107 108 Returns: 109 The calculated, or given minimum width of this object. 110 """ 111 112 return self._min_width or self._auto_min_width 113 114 @min_width.setter 115 def min_width(self, new: int | None) -> None: 116 """Sets a new minimum width.""" 117 118 self._min_width = new 119 120 @property 121 def rect(self) -> tuple[int, int, int, int]: 122 """Returns the tuple of positions that define this window. 123 124 Returns: 125 A tuple of integers, in the order (left, top, right, bottom). 126 """ 127 128 left, top = self.pos 129 return (left, top, left + self.width, top + self.height) 130 131 @rect.setter 132 def rect(self, new: tuple[int, int, int, int]) -> None: 133 """Sets new position, width and height of this window. 134 135 This method also checks for the minimum width this window can be, and 136 if the new width doesn't comply with that setting the changes are thrown 137 away. 138 139 Args: 140 new: A tuple of integers in the order (left, top, right, bottom). 141 """ 142 143 left, top, right, bottom = new 144 minimum = self.min_width or 0 145 146 if right - left < minimum: 147 return 148 149 # Update size policy to fill to resize inner objects properly 150 self.size_policy = SizePolicy.FILL 151 self.pos = (left, top) 152 self.width = right - left 153 self.height = bottom - top 154 155 # Restore original size policy 156 self.size_policy = SizePolicy.STATIC 157 158 def __iadd__(self, other: object) -> Window: 159 """Calls self._add_widget(other) and returns self.""" 160 161 self._add_widget(other) 162 return self 163 164 def __add__(self, other: object) -> Window: 165 """Calls self._add_widget(other) and returns self.""" 166 167 self._add_widget(other) 168 return self 169 170 def _add_widget(self, other: object, run_get_lines: bool = True) -> Widget: 171 """Adds a widget to the window. 172 173 Args: 174 other: The widget-like to add. 175 run_get_lines: Whether self.get_lines should be ran after adding. 176 """ 177 178 added = super()._add_widget(other, run_get_lines) 179 180 if len(self._widgets) > 0: 181 self._auto_min_width = max(widget.width for widget in self._widgets) 182 self._auto_min_width += self.sidelength 183 184 self.height += added.height 185 186 return added 187 188 @classmethod 189 def set_focus_styles( 190 cls, 191 *, 192 focused: tuple[w_styles.StyleValue, w_styles.StyleValue], 193 blurred: tuple[w_styles.StyleValue, w_styles.StyleValue], 194 ) -> None: 195 """Sets focused & blurred border & corner styles. 196 197 Args: 198 focused: A tuple of border_focused, corner_focused styles. 199 blurred: A tuple of border_blurred, corner_blurred styles. 200 """ 201 202 cls.styles.border_focused, cls.styles.corner_focused = focused 203 cls.styles.border_blurred, cls.styles.corner_blurred = blurred 204 205 def focus(self) -> None: 206 """Focuses this window.""" 207 208 self.has_focus = True 209 210 if not self.is_noblur: 211 self.styles.border = self.styles.border_focused 212 self.styles.corner = self.styles.corner_focused 213 214 def blur(self) -> None: 215 """Blurs (unfocuses) this window.""" 216 217 self.has_focus = False 218 self.select(None) 219 self.handle_mouse(MouseEvent(MouseAction.RELEASE, (0, 0))) 220 221 if not self.is_noblur: 222 self.styles.border = self.styles.border_blurred 223 self.styles.corner = self.styles.corner_blurred 224 225 def clear_cache(self) -> None: 226 """Clears manager compositor's cached blur state.""" 227 228 if self.manager is not None: 229 self.manager.clear_cache(self) 230 231 def contains(self, pos: tuple[int, int]) -> bool: 232 """Determines whether widget contains `pos`. 233 234 This method uses window.rect to get the positions. 235 236 Args: 237 pos: Position to compare. 238 239 Returns: 240 Boolean describing whether the position is inside 241 this widget. 242 """ 243 244 left, top, right, bottom = self.rect 245 246 return left <= pos[0] < right and top <= pos[1] < bottom 247 248 def set_title(self, title: str, position: int = 0, pad: bool = True) -> Window: 249 """Sets the window's title. 250 251 Args: 252 title: The string to set as the window title. 253 position: An integer indexing into ["left", "top", "right", "bottom"], 254 determining where the title is applied. 255 pad: Whether there should be an extra space before and after the given title. 256 defaults to True. 257 """ 258 259 self.title = title 260 261 if pad: 262 title = " " + title + " " 263 264 corners = self._get_char("corner") 265 assert isinstance(corners, list) 266 267 if position % 2 == 0: 268 corners[position] += title 269 270 else: 271 current = corners[position] 272 corners[position] = title + current 273 274 self.set_char("corner", corners) 275 276 return self 277 278 def center( 279 self, where: CenteringPolicy | None = None, store: bool = True 280 ) -> Window: 281 """Center window""" 282 283 super().center(where, store) 284 return self 285 286 def close(self, animate: bool = True) -> None: 287 """Instruct window manager to close object""" 288 289 assert self.manager is not None 290 291 self.manager.remove(self, animate=animate)
A class representing a window.
Windows are essentially fancy pytermgui.widgets.Container
-s. They build on top of them
to store and display various widgets, while allowing some custom functionality.
66 def __init__(self, *widgets: Any, **attrs: Any) -> None: 67 """Initializes object. 68 69 Args: 70 widgets: Widgets to add to this window after initilization. 71 attrs: Attributes that are passed to the constructor. 72 """ 73 74 self._min_width: int | None = None 75 self._auto_min_width: int | None = None 76 77 self.styles.border_focused = type(self).styles.border 78 self.styles.corner_focused = type(self).styles.corner 79 80 super().__init__(*widgets, **attrs) 81 82 self.has_focus: bool = False 83 84 self.manager: "WindowManager" | None = None 85 86 # ------------------------- position ----- width x height 87 self._restore_data: tuple[tuple[int, int], tuple[int, int]] | None = None 88 89 if self.title != "": 90 self.set_title(self.title) 91 92 if self.is_persistent: 93 self.is_noblur = True
Initializes object.
Args
- widgets: Widgets to add to this window after initilization.
- attrs: Attributes that are passed to the constructor.
Modal windows stay on top of every other window and block interactions with other windows.
No-blur windows will always appear to stay in focus, even if they functionally don't.
Persistent windows will be set noblur automatically, and remain clickable even through modals.
While the library core doesn't do this for various reasons, it also might be useful to disable some behaviour (e.g. closing) for persistent windows on an implementation level.
Default characters for this class
Default styles for this class
Minimum width of the window.
If set to none, _auto_min_width will be calculated based on the maximum width of inner widgets.
This is accurate enough for general use, but tends to lean to the safer side, i.e. it often overshoots the 'real' minimum width possible.
If you find this to be the case, AND you can ensure that your window will not break, you may set this value manually.
Returns
The calculated, or given minimum width of this object.
Returns the tuple of positions that define this window.
Returns
A tuple of integers, in the order (left, top, right, bottom).
188 @classmethod 189 def set_focus_styles( 190 cls, 191 *, 192 focused: tuple[w_styles.StyleValue, w_styles.StyleValue], 193 blurred: tuple[w_styles.StyleValue, w_styles.StyleValue], 194 ) -> None: 195 """Sets focused & blurred border & corner styles. 196 197 Args: 198 focused: A tuple of border_focused, corner_focused styles. 199 blurred: A tuple of border_blurred, corner_blurred styles. 200 """ 201 202 cls.styles.border_focused, cls.styles.corner_focused = focused 203 cls.styles.border_blurred, cls.styles.corner_blurred = blurred
Sets focused & blurred border & corner styles.
Args
- focused: A tuple of border_focused, corner_focused styles.
- blurred: A tuple of border_blurred, corner_blurred styles.
205 def focus(self) -> None: 206 """Focuses this window.""" 207 208 self.has_focus = True 209 210 if not self.is_noblur: 211 self.styles.border = self.styles.border_focused 212 self.styles.corner = self.styles.corner_focused
Focuses this window.
214 def blur(self) -> None: 215 """Blurs (unfocuses) this window.""" 216 217 self.has_focus = False 218 self.select(None) 219 self.handle_mouse(MouseEvent(MouseAction.RELEASE, (0, 0))) 220 221 if not self.is_noblur: 222 self.styles.border = self.styles.border_blurred 223 self.styles.corner = self.styles.corner_blurred
Blurs (unfocuses) this window.
225 def clear_cache(self) -> None: 226 """Clears manager compositor's cached blur state.""" 227 228 if self.manager is not None: 229 self.manager.clear_cache(self)
Clears manager compositor's cached blur state.
231 def contains(self, pos: tuple[int, int]) -> bool: 232 """Determines whether widget contains `pos`. 233 234 This method uses window.rect to get the positions. 235 236 Args: 237 pos: Position to compare. 238 239 Returns: 240 Boolean describing whether the position is inside 241 this widget. 242 """ 243 244 left, top, right, bottom = self.rect 245 246 return left <= pos[0] < right and top <= pos[1] < bottom
Determines whether widget contains pos
.
This method uses window.rect to get the positions.
Args
- pos: Position to compare.
Returns
Boolean describing whether the position is inside this widget.
248 def set_title(self, title: str, position: int = 0, pad: bool = True) -> Window: 249 """Sets the window's title. 250 251 Args: 252 title: The string to set as the window title. 253 position: An integer indexing into ["left", "top", "right", "bottom"], 254 determining where the title is applied. 255 pad: Whether there should be an extra space before and after the given title. 256 defaults to True. 257 """ 258 259 self.title = title 260 261 if pad: 262 title = " " + title + " " 263 264 corners = self._get_char("corner") 265 assert isinstance(corners, list) 266 267 if position % 2 == 0: 268 corners[position] += title 269 270 else: 271 current = corners[position] 272 corners[position] = title + current 273 274 self.set_char("corner", corners) 275 276 return self
Sets the window's title.
Args
- title: The string to set as the window title.
- position: An integer indexing into ["left", "top", "right", "bottom"], determining where the title is applied.
- pad: Whether there should be an extra space before and after the given title. defaults to True.
278 def center( 279 self, where: CenteringPolicy | None = None, store: bool = True 280 ) -> Window: 281 """Center window""" 282 283 super().center(where, store) 284 return self
Center window
286 def close(self, animate: bool = True) -> None: 287 """Instruct window manager to close object""" 288 289 assert self.manager is not None 290 291 self.manager.remove(self, animate=animate)
Instruct window manager to close object
Inherited Members
- pytermgui.widgets.containers.Container
- keys
- serialized
- vertical_align
- allow_fullscreen
- sidelength
- content_dimensions
- selectables
- selectables_length
- selected
- box
- get_change
- lazy_add
- get_lines
- set_widgets
- serialize
- pop
- remove
- set_recursive_depth
- select
- handle_mouse
- execute_binding
- handle_key
- wipe
- debug