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)
class Window(pytermgui.widgets.containers.Container):
 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.

Window(*widgets: Any, **attrs: Any)
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.
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:
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.
def focus(self) -> None:
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.

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

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

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

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

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