Skip to content

window

The Window class, which is an implementation of pytermgui.widgets.Container that allows for mouse-based moving and resizing.

Window

Bases: Container

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.

Source code in pytermgui/window_manager/window.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
class Window(Container):  # pylint: disable=too-many-instance-attributes
    """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.
    """

    overflow = Overflow.HIDE

    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 = Container.chars.copy()

    styles = w_styles.StyleManager(
        border="surface",
        corner="surface",
        fill="background",
        border_focused="surface",
        corner_focused="surface",
        border_blurred="surface-2",
        corner_blurred="surface-2",
    )

    def __init__(self, *widgets: Any, **attrs: Any) -> None:
        """Initializes object.

        Args:
            *widgets: Widgets to add to this window after initilization.
            **attrs: Attributes that are passed to the constructor.
        """

        self._min_width: int | None = None
        self._auto_min_width: int | None = None

        self.styles.border_focused = type(self).styles.border
        self.styles.corner_focused = type(self).styles.corner

        if "box" not in attrs:
            attrs["box"] = "DOUBLE"

        super().__init__(*widgets, **attrs)

        self.has_focus: bool = False

        self.manager: "WindowManager" | None = None

        # -------------------------  position ----- width x height
        self._restore_data: tuple[tuple[int, int], tuple[int, int]] | None = None

        if self.title != "":
            self.set_title(self.title)

        if self.is_persistent:
            self.is_noblur = True

    @property
    def min_width(self) -> 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.
        """

        return self._min_width or self._auto_min_width

    @min_width.setter
    def min_width(self, new: int | None) -> None:
        """Sets a new minimum width."""

        self._min_width = new

    @property
    def rect(self) -> 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).
        """

        left, top = self.pos
        return (left, top, left + self.width, top + self.height)

    @rect.setter
    def rect(self, new: tuple[int, int, int, int]) -> None:
        """Sets new position, width and height of this window.

        This method also checks for the minimum width this window can be, and
        if the new width doesn't comply with that setting the changes are thrown
        away.

        Args:
            new: A tuple of integers in the order (left, top, right, bottom).
        """

        left, top, right, bottom = new
        minimum = self.min_width or 0

        if right - left < minimum:
            return

        # Update size policy to fill to resize inner objects properly
        self.size_policy = SizePolicy.FILL
        self.pos = (left, top)
        self.width = right - left
        self.height = bottom - top

        # Restore original size policy
        self.size_policy = SizePolicy.STATIC

    def __iadd__(self, other: object) -> Window:
        """Calls self._add_widget(other) and returns self."""

        self._add_widget(other)
        return self

    def __add__(self, other: object) -> Window:
        """Calls self._add_widget(other) and returns self."""

        self._add_widget(other)
        return self

    def _add_widget(self, other: object, run_get_lines: bool = True) -> Widget:
        """Adds a widget to the window.

        Args:
            other: The widget-like to add.
            run_get_lines: Whether self.get_lines should be ran after adding.
        """

        added = super()._add_widget(other, run_get_lines)

        if len(self._widgets) > 0:
            self._auto_min_width = max(widget.width for widget in self._widgets)
            self._auto_min_width += self.sidelength

        self.height += added.height

        return added

    @classmethod
    def set_focus_styles(
        cls,
        *,
        focused: tuple[w_styles.StyleValue, w_styles.StyleValue],
        blurred: tuple[w_styles.StyleValue, w_styles.StyleValue],
    ) -> None:
        """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.
        """

        cls.styles.border_focused, cls.styles.corner_focused = focused
        cls.styles.border_blurred, cls.styles.corner_blurred = blurred

    def focus(self) -> None:
        """Focuses this window."""

        self.has_focus = True

        if not self.is_noblur:
            self.styles.border = self.styles.border_focused
            self.styles.corner = self.styles.corner_focused

    def blur(self) -> None:
        """Blurs (unfocuses) this window."""

        self.has_focus = False
        self.select(None)
        self.handle_mouse(MouseEvent(MouseAction.RELEASE, (0, 0)))

        if not self.is_noblur:
            self.styles.border = self.styles.border_blurred
            self.styles.corner = self.styles.corner_blurred

    def clear_cache(self) -> None:
        """Clears manager compositor's cached blur state."""

        if self.manager is not None:
            self.manager.clear_cache(self)

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

        left, top, right, bottom = self.rect

        return left <= pos[0] < right and top <= pos[1] < bottom

    def set_title(self, title: str, position: int = 0, pad: bool = True) -> Window:
        """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.
        """

        corners = self._get_char("corner")
        assert isinstance(corners, list)

        # Delete (both cases) of current title from the corner
        corners[position] = (
            corners[position].replace(f" {self.title} ", "").replace(self.title, "")
        )

        self.title = title

        if pad:
            title = " " + title + " "

        if position % 2 == 0:
            corners[position] += title

        else:
            current = corners[position]
            corners[position] = title + current

        self.set_char("corner", corners)

        return self

    def center(
        self, where: CenteringPolicy | None = None, store: bool = True
    ) -> Window:
        """Center window"""

        super().center(where, store)
        return self

    def close(self, animate: bool = True) -> None:
        """Instruct window manager to close object"""

        assert self.manager is not None

        self.manager.remove(self, animate=animate)

is_dirty = False class-attribute instance-attribute

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

is_modal = False class-attribute instance-attribute

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

is_noblur = False class-attribute instance-attribute

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

is_noresize = False class-attribute instance-attribute

No-resize windows cannot be resized using the mouse.

is_persistent = False class-attribute instance-attribute

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.

is_static = False class-attribute instance-attribute

Static windows cannot be moved using the mouse.

min_width: int | None property writable

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:

Type Description
int | None

The calculated, or given minimum width of this object.

rect: tuple[int, int, int, int] property writable

Returns the tuple of positions that define this window.

Returns:

Type Description
tuple[int, int, int, int]

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

title = '' class-attribute instance-attribute

Title shown in left-top corner.

__add__(other)

Calls self._add_widget(other) and returns self.

Source code in pytermgui/window_manager/window.py
165
166
167
168
169
def __add__(self, other: object) -> Window:
    """Calls self._add_widget(other) and returns self."""

    self._add_widget(other)
    return self

__iadd__(other)

Calls self._add_widget(other) and returns self.

Source code in pytermgui/window_manager/window.py
159
160
161
162
163
def __iadd__(self, other: object) -> Window:
    """Calls self._add_widget(other) and returns self."""

    self._add_widget(other)
    return self

__init__(*widgets, **attrs)

Initializes object.

Parameters:

Name Type Description Default
*widgets Any

Widgets to add to this window after initilization.

()
**attrs Any

Attributes that are passed to the constructor.

{}
Source code in pytermgui/window_manager/window.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def __init__(self, *widgets: Any, **attrs: Any) -> None:
    """Initializes object.

    Args:
        *widgets: Widgets to add to this window after initilization.
        **attrs: Attributes that are passed to the constructor.
    """

    self._min_width: int | None = None
    self._auto_min_width: int | None = None

    self.styles.border_focused = type(self).styles.border
    self.styles.corner_focused = type(self).styles.corner

    if "box" not in attrs:
        attrs["box"] = "DOUBLE"

    super().__init__(*widgets, **attrs)

    self.has_focus: bool = False

    self.manager: "WindowManager" | None = None

    # -------------------------  position ----- width x height
    self._restore_data: tuple[tuple[int, int], tuple[int, int]] | None = None

    if self.title != "":
        self.set_title(self.title)

    if self.is_persistent:
        self.is_noblur = True

blur()

Blurs (unfocuses) this window.

Source code in pytermgui/window_manager/window.py
215
216
217
218
219
220
221
222
223
224
def blur(self) -> None:
    """Blurs (unfocuses) this window."""

    self.has_focus = False
    self.select(None)
    self.handle_mouse(MouseEvent(MouseAction.RELEASE, (0, 0)))

    if not self.is_noblur:
        self.styles.border = self.styles.border_blurred
        self.styles.corner = self.styles.corner_blurred

center(where=None, store=True)

Center window

Source code in pytermgui/window_manager/window.py
284
285
286
287
288
289
290
def center(
    self, where: CenteringPolicy | None = None, store: bool = True
) -> Window:
    """Center window"""

    super().center(where, store)
    return self

clear_cache()

Clears manager compositor's cached blur state.

Source code in pytermgui/window_manager/window.py
226
227
228
229
230
def clear_cache(self) -> None:
    """Clears manager compositor's cached blur state."""

    if self.manager is not None:
        self.manager.clear_cache(self)

close(animate=True)

Instruct window manager to close object

Source code in pytermgui/window_manager/window.py
292
293
294
295
296
297
def close(self, animate: bool = True) -> None:
    """Instruct window manager to close object"""

    assert self.manager is not None

    self.manager.remove(self, animate=animate)

contains(pos)

Determines whether widget contains pos.

This method uses window.rect to get the positions.

Parameters:

Name Type Description Default
pos tuple[int, int]

Position to compare.

required

Returns:

Type Description
bool

Boolean describing whether the position is inside this widget.

Source code in pytermgui/window_manager/window.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def contains(self, pos: tuple[int, int]) -> bool:
    """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.
    """

    left, top, right, bottom = self.rect

    return left <= pos[0] < right and top <= pos[1] < bottom

focus()

Focuses this window.

Source code in pytermgui/window_manager/window.py
206
207
208
209
210
211
212
213
def focus(self) -> None:
    """Focuses this window."""

    self.has_focus = True

    if not self.is_noblur:
        self.styles.border = self.styles.border_focused
        self.styles.corner = self.styles.corner_focused

set_focus_styles(*, focused, blurred) classmethod

Sets focused & blurred border & corner styles.

Parameters:

Name Type Description Default
focused tuple[w_styles.StyleValue, w_styles.StyleValue]

A tuple of border_focused, corner_focused styles.

required
blurred tuple[w_styles.StyleValue, w_styles.StyleValue]

A tuple of border_blurred, corner_blurred styles.

required
Source code in pytermgui/window_manager/window.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
@classmethod
def set_focus_styles(
    cls,
    *,
    focused: tuple[w_styles.StyleValue, w_styles.StyleValue],
    blurred: tuple[w_styles.StyleValue, w_styles.StyleValue],
) -> None:
    """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.
    """

    cls.styles.border_focused, cls.styles.corner_focused = focused
    cls.styles.border_blurred, cls.styles.corner_blurred = blurred

set_title(title, position=0, pad=True)

Sets the window's title.

Parameters:

Name Type Description Default
title str

The string to set as the window title.

required
position int

An integer indexing into ["left", "top", "right", "bottom"], determining where the title is applied.

0
pad bool

Whether there should be an extra space before and after the given title. defaults to True.

True
Source code in pytermgui/window_manager/window.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def set_title(self, title: str, position: int = 0, pad: bool = True) -> Window:
    """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.
    """

    corners = self._get_char("corner")
    assert isinstance(corners, list)

    # Delete (both cases) of current title from the corner
    corners[position] = (
        corners[position].replace(f" {self.title} ", "").replace(self.title, "")
    )

    self.title = title

    if pad:
        title = " " + title + " "

    if position % 2 == 0:
        corners[position] += title

    else:
        current = corners[position]
        corners[position] = title + current

    self.set_char("corner", corners)

    return self