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