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.

#   Window(*widgets: Any, **attrs: Any)
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.
#   is_bindable = True

Allow binding support

#   overflow = <Overflow.HIDE: 0>
#   title = ''

Title shown in left-top corner.

#   is_static = False

Static windows cannot be moved using the mouse.

#   is_modal = False

Modal windows stay on top of every other window and block interactions with other windows.

#   is_noblur = False

No-blur windows will always appear to stay in focus, even if they functionally don't.

#   is_noresize = False

No-resize windows cannot be resized using the mouse.

#   is_dirty = False

Controls whether the window should be redrawn in the next frame.

#   is_persistent = False

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.

#   chars: dict[str, typing.Union[typing.List[str], str]] = {'border': ['| ', '-', ' |', '-'], 'corner': ['', '', '', '']}

Default characters for this class

#   styles = {'border': StyleCall(obj=None, method=<function <lambda>>), 'corner': StyleCall(obj=None, method=<function <lambda>>), 'fill': StyleCall(obj=None, method=MarkupFormatter(markup='{item}', ensure_strip=False, _markup_cache={})), 'border_focused': StyleCall(obj=None, method=<function <lambda>>), 'corner_focused': StyleCall(obj=None, method=<function <lambda>>), 'border_blurred': StyleCall(obj=None, method=MarkupFormatter(markup='[238]{item}', ensure_strip=False, _markup_cache={})), 'corner_blurred': StyleCall(obj=None, method=MarkupFormatter(markup='[238]{item}', ensure_strip=False, _markup_cache={}))}

Default styles for this class

#   min_width: int | None

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.

#   rect: tuple[int, int, int, int]

Returns the tuple of positions that define this window.

Returns

A tuple of integers, in the order (left, top, right, bottom).

#  
@classmethod
def set_focus_styles( cls, *, focused: 'tuple[w_styles.StyleValue, w_styles.StyleValue]', blurred: 'tuple[w_styles.StyleValue, w_styles.StyleValue]' ) -> None:
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.
#   def focus(self) -> None:
View Source
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

Focuses this window.

#   def blur(self) -> None:
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.

#   def clear_cache(self) -> None:
View Source
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)

Clears manager compositor's cached blur state.

#   def contains(self, pos: tuple[int, int]) -> bool:
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.

#   def set_title(self, title: str, position: int = 0, pad: bool = True) -> None:
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.
#   def center( self, where: pytermgui.enums.CenteringPolicy | None = None, store: bool = True ) -> pytermgui.window_manager.window.Window:
View Source
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

Center window

#   def close(self, animate: bool = True) -> None:
View Source
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)

Instruct window manager to close object