euporie 2.6.1__py3-none-any.whl → 2.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- euporie/console/tabs/console.py +51 -43
- euporie/core/__init__.py +5 -2
- euporie/core/app.py +74 -57
- euporie/core/comm/ipywidgets.py +7 -3
- euporie/core/config.py +51 -27
- euporie/core/convert/__init__.py +2 -0
- euporie/core/convert/datum.py +82 -45
- euporie/core/convert/formats/ansi.py +1 -2
- euporie/core/convert/formats/common.py +7 -11
- euporie/core/convert/formats/ft.py +10 -7
- euporie/core/convert/formats/png.py +7 -6
- euporie/core/convert/formats/sixel.py +1 -1
- euporie/core/convert/formats/svg.py +28 -0
- euporie/core/convert/mime.py +4 -7
- euporie/core/data_structures.py +24 -22
- euporie/core/filters.py +16 -2
- euporie/core/format.py +30 -4
- euporie/core/ft/ansi.py +2 -1
- euporie/core/ft/html.py +155 -42
- euporie/core/{widgets/graphics.py → graphics.py} +225 -227
- euporie/core/io.py +8 -0
- euporie/core/key_binding/bindings/__init__.py +8 -2
- euporie/core/key_binding/bindings/basic.py +9 -14
- euporie/core/key_binding/bindings/micro.py +0 -12
- euporie/core/key_binding/bindings/mouse.py +107 -80
- euporie/core/key_binding/bindings/page_navigation.py +129 -0
- euporie/core/key_binding/key_processor.py +9 -1
- euporie/core/layout/__init__.py +1 -0
- euporie/core/layout/containers.py +1011 -0
- euporie/core/layout/decor.py +381 -0
- euporie/core/layout/print.py +130 -0
- euporie/core/layout/screen.py +75 -0
- euporie/core/{widgets/page.py → layout/scroll.py} +166 -111
- euporie/core/log.py +1 -1
- euporie/core/margins.py +11 -5
- euporie/core/path.py +43 -176
- euporie/core/renderer.py +31 -8
- euporie/core/style.py +2 -0
- euporie/core/tabs/base.py +2 -1
- euporie/core/terminal.py +19 -21
- euporie/core/widgets/cell.py +2 -4
- euporie/core/widgets/cell_outputs.py +2 -2
- euporie/core/widgets/decor.py +3 -359
- euporie/core/widgets/dialog.py +5 -5
- euporie/core/widgets/display.py +32 -12
- euporie/core/widgets/file_browser.py +3 -4
- euporie/core/widgets/forms.py +36 -14
- euporie/core/widgets/inputs.py +171 -99
- euporie/core/widgets/layout.py +80 -5
- euporie/core/widgets/menu.py +1 -3
- euporie/core/widgets/pager.py +3 -3
- euporie/core/widgets/palette.py +3 -2
- euporie/core/widgets/status_bar.py +2 -6
- euporie/core/widgets/tree.py +3 -6
- euporie/notebook/app.py +8 -8
- euporie/notebook/tabs/notebook.py +2 -2
- euporie/notebook/widgets/side_bar.py +1 -1
- euporie/preview/tabs/notebook.py +2 -2
- euporie/web/tabs/web.py +6 -1
- euporie/web/widgets/webview.py +52 -32
- {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/METADATA +9 -11
- {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/RECORD +67 -60
- {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/WHEEL +1 -1
- {euporie-2.6.1.data → euporie-2.7.0.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.6.1.data → euporie-2.7.0.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/entry_points.txt +0 -0
- {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1011 @@
|
|
1
|
+
"""Overrides for PTK containers which only render visible lines."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from functools import lru_cache, partial
|
7
|
+
from typing import TYPE_CHECKING
|
8
|
+
|
9
|
+
from prompt_toolkit.application.current import get_app
|
10
|
+
from prompt_toolkit.data_structures import Point
|
11
|
+
from prompt_toolkit.layout import containers
|
12
|
+
from prompt_toolkit.layout.containers import WindowAlign, WindowRenderInfo
|
13
|
+
from prompt_toolkit.layout.controls import (
|
14
|
+
FormattedTextControl,
|
15
|
+
UIContent,
|
16
|
+
fragment_list_width,
|
17
|
+
to_formatted_text,
|
18
|
+
)
|
19
|
+
from prompt_toolkit.layout.dimension import sum_layout_dimensions
|
20
|
+
from prompt_toolkit.layout.screen import _CHAR_CACHE
|
21
|
+
from prompt_toolkit.layout.utils import explode_text_fragments
|
22
|
+
from prompt_toolkit.mouse_events import MouseEvent
|
23
|
+
from prompt_toolkit.utils import get_cwidth, take_using_weights, to_str
|
24
|
+
|
25
|
+
from euporie.core.data_structures import DiInt
|
26
|
+
from euporie.core.layout.screen import BoundedWritePosition
|
27
|
+
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from typing import Callable
|
30
|
+
|
31
|
+
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
|
32
|
+
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
33
|
+
from prompt_toolkit.layout.containers import Float
|
34
|
+
from prompt_toolkit.layout.dimension import Dimension
|
35
|
+
from prompt_toolkit.layout.margins import Margin
|
36
|
+
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
|
37
|
+
from prompt_toolkit.layout.screen import Screen, WritePosition
|
38
|
+
|
39
|
+
|
40
|
+
log = logging.getLogger(__name__)
|
41
|
+
|
42
|
+
|
43
|
+
class HSplit(containers.HSplit):
|
44
|
+
"""Several layouts, one stacked above/under the other."""
|
45
|
+
|
46
|
+
def write_to_screen(
|
47
|
+
self,
|
48
|
+
screen: Screen,
|
49
|
+
mouse_handlers: MouseHandlers,
|
50
|
+
write_position: WritePosition,
|
51
|
+
parent_style: str,
|
52
|
+
erase_bg: bool,
|
53
|
+
z_index: int | None,
|
54
|
+
) -> None:
|
55
|
+
"""Render the prompt to a `Screen` instance.
|
56
|
+
|
57
|
+
:param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class
|
58
|
+
to which the output has to be written.
|
59
|
+
"""
|
60
|
+
assert isinstance(write_position, BoundedWritePosition)
|
61
|
+
sizes = self._divide_heights(write_position)
|
62
|
+
style = parent_style + " " + to_str(self.style)
|
63
|
+
z_index = z_index if self.z_index is None else self.z_index
|
64
|
+
|
65
|
+
if sizes is None:
|
66
|
+
self.window_too_small.write_to_screen(
|
67
|
+
screen, mouse_handlers, write_position, style, erase_bg, z_index
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
#
|
71
|
+
ypos = write_position.ypos
|
72
|
+
xpos = write_position.xpos
|
73
|
+
width = write_position.width
|
74
|
+
bbox = write_position.bbox
|
75
|
+
|
76
|
+
# Draw child panes.
|
77
|
+
for s, c in zip(sizes, self._all_children):
|
78
|
+
c.write_to_screen(
|
79
|
+
screen,
|
80
|
+
mouse_handlers,
|
81
|
+
BoundedWritePosition(
|
82
|
+
xpos,
|
83
|
+
ypos,
|
84
|
+
width,
|
85
|
+
s,
|
86
|
+
bbox=DiInt(
|
87
|
+
top=max(0, bbox.top - (ypos - write_position.ypos)),
|
88
|
+
right=bbox.right,
|
89
|
+
bottom=max(
|
90
|
+
0,
|
91
|
+
bbox.bottom
|
92
|
+
- (
|
93
|
+
write_position.ypos
|
94
|
+
+ write_position.height
|
95
|
+
- ypos
|
96
|
+
- s
|
97
|
+
),
|
98
|
+
),
|
99
|
+
left=bbox.left,
|
100
|
+
),
|
101
|
+
),
|
102
|
+
style,
|
103
|
+
erase_bg,
|
104
|
+
z_index,
|
105
|
+
)
|
106
|
+
ypos += s
|
107
|
+
|
108
|
+
# Fill in the remaining space. This happens when a child control
|
109
|
+
# refuses to take more space and we don't have any padding. Adding a
|
110
|
+
# dummy child control for this (in `self._all_children`) is not
|
111
|
+
# desired, because in some situations, it would take more space, even
|
112
|
+
# when it's not required. This is required to apply the styling.
|
113
|
+
remaining_height = write_position.ypos + write_position.height - ypos
|
114
|
+
if remaining_height > 0:
|
115
|
+
self._remaining_space_window.write_to_screen(
|
116
|
+
screen,
|
117
|
+
mouse_handlers,
|
118
|
+
BoundedWritePosition(
|
119
|
+
xpos,
|
120
|
+
ypos,
|
121
|
+
width,
|
122
|
+
remaining_height,
|
123
|
+
bbox=DiInt(
|
124
|
+
top=max(0, bbox.top - (ypos - write_position.ypos)),
|
125
|
+
right=bbox.right,
|
126
|
+
bottom=min(bbox.bottom, remaining_height),
|
127
|
+
left=bbox.left,
|
128
|
+
),
|
129
|
+
),
|
130
|
+
style,
|
131
|
+
erase_bg,
|
132
|
+
z_index,
|
133
|
+
)
|
134
|
+
|
135
|
+
def _divide_heights(self, write_position: WritePosition) -> list[int] | None:
|
136
|
+
"""Return the heights for all rows, or None when there is not enough space."""
|
137
|
+
if not self.children:
|
138
|
+
return []
|
139
|
+
|
140
|
+
# Calculate heights.
|
141
|
+
width = write_position.width
|
142
|
+
height = write_position.height
|
143
|
+
|
144
|
+
return _get_divided_heights(
|
145
|
+
width,
|
146
|
+
height,
|
147
|
+
tuple(c.preferred_height(width, height) for c in self._all_children),
|
148
|
+
)
|
149
|
+
|
150
|
+
|
151
|
+
@lru_cache(maxsize=2048)
|
152
|
+
def _get_divided_heights(
|
153
|
+
width: int, height: int, dimensions: tuple[Dimension, ...]
|
154
|
+
) -> list[int] | None:
|
155
|
+
# Sum dimensions
|
156
|
+
sum_dimensions = sum_layout_dimensions(list(dimensions))
|
157
|
+
|
158
|
+
# If there is not enough space for both.
|
159
|
+
# Don't do anything.
|
160
|
+
if sum_dimensions.min > height:
|
161
|
+
return None
|
162
|
+
|
163
|
+
# Find optimal sizes. (Start with minimal size, increase until we cover
|
164
|
+
# the whole height.)
|
165
|
+
sizes = [d.min for d in dimensions]
|
166
|
+
|
167
|
+
child_generator = take_using_weights(
|
168
|
+
items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]
|
169
|
+
)
|
170
|
+
|
171
|
+
i = next(child_generator)
|
172
|
+
|
173
|
+
# Increase until we meet at least the 'preferred' size.
|
174
|
+
preferred_stop = min(height, sum_dimensions.preferred)
|
175
|
+
preferred_dimensions = [d.preferred for d in dimensions]
|
176
|
+
|
177
|
+
while sum(sizes) < preferred_stop:
|
178
|
+
if sizes[i] < preferred_dimensions[i]:
|
179
|
+
sizes[i] += 1
|
180
|
+
i = next(child_generator)
|
181
|
+
|
182
|
+
# Increase until we use all the available space. (or until "max")
|
183
|
+
if not get_app().is_done:
|
184
|
+
max_stop = min(height, sum_dimensions.max)
|
185
|
+
max_dimensions = [d.max for d in dimensions]
|
186
|
+
|
187
|
+
while sum(sizes) < max_stop:
|
188
|
+
if sizes[i] < max_dimensions[i]:
|
189
|
+
sizes[i] += 1
|
190
|
+
i = next(child_generator)
|
191
|
+
|
192
|
+
return sizes
|
193
|
+
|
194
|
+
|
195
|
+
class VSplit(containers.VSplit):
|
196
|
+
"""Several layouts, one stacked left/right of the other."""
|
197
|
+
|
198
|
+
def write_to_screen(
|
199
|
+
self,
|
200
|
+
screen: Screen,
|
201
|
+
mouse_handlers: MouseHandlers,
|
202
|
+
write_position: WritePosition,
|
203
|
+
parent_style: str,
|
204
|
+
erase_bg: bool,
|
205
|
+
z_index: int | None,
|
206
|
+
) -> None:
|
207
|
+
"""Render the prompt to a `Screen` instance.
|
208
|
+
|
209
|
+
:param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class
|
210
|
+
to which the output has to be written.
|
211
|
+
"""
|
212
|
+
assert isinstance(write_position, BoundedWritePosition)
|
213
|
+
if not self.children:
|
214
|
+
return
|
215
|
+
|
216
|
+
children = self._all_children
|
217
|
+
sizes = self._divide_widths(write_position.width)
|
218
|
+
style = parent_style + " " + to_str(self.style)
|
219
|
+
z_index = z_index if self.z_index is None else self.z_index
|
220
|
+
|
221
|
+
# If there is not enough space.
|
222
|
+
if sizes is None:
|
223
|
+
self.window_too_small.write_to_screen(
|
224
|
+
screen, mouse_handlers, write_position, style, erase_bg, z_index
|
225
|
+
)
|
226
|
+
return
|
227
|
+
|
228
|
+
# Calculate heights, take the largest possible, but not larger than
|
229
|
+
# write_position.height.
|
230
|
+
heights = [
|
231
|
+
child.preferred_height(width, write_position.height).preferred
|
232
|
+
for width, child in zip(sizes, children)
|
233
|
+
]
|
234
|
+
height = max(write_position.height, min(write_position.height, max(heights)))
|
235
|
+
|
236
|
+
#
|
237
|
+
ypos = write_position.ypos
|
238
|
+
xpos = write_position.xpos
|
239
|
+
bbox = write_position.bbox
|
240
|
+
|
241
|
+
# Draw all child panes.
|
242
|
+
for s, c in zip(sizes, children):
|
243
|
+
c.write_to_screen(
|
244
|
+
screen,
|
245
|
+
mouse_handlers,
|
246
|
+
BoundedWritePosition(
|
247
|
+
xpos,
|
248
|
+
ypos,
|
249
|
+
s,
|
250
|
+
height,
|
251
|
+
DiInt(
|
252
|
+
top=bbox.top,
|
253
|
+
right=max(
|
254
|
+
bbox.right,
|
255
|
+
xpos + s - write_position.xpos - write_position.width,
|
256
|
+
),
|
257
|
+
bottom=bbox.bottom,
|
258
|
+
left=max(0, bbox.left - write_position.xpos - xpos),
|
259
|
+
),
|
260
|
+
),
|
261
|
+
style,
|
262
|
+
erase_bg,
|
263
|
+
z_index,
|
264
|
+
)
|
265
|
+
xpos += s
|
266
|
+
|
267
|
+
# Fill in the remaining space. This happens when a child control
|
268
|
+
# refuses to take more space and we don't have any padding. Adding a
|
269
|
+
# dummy child control for this (in `self._all_children`) is not
|
270
|
+
# desired, because in some situations, it would take more space, even
|
271
|
+
# when it's not required. This is required to apply the styling.
|
272
|
+
remaining_width = write_position.xpos + write_position.width - xpos
|
273
|
+
if remaining_width > 0:
|
274
|
+
self._remaining_space_window.write_to_screen(
|
275
|
+
screen,
|
276
|
+
mouse_handlers,
|
277
|
+
BoundedWritePosition(
|
278
|
+
xpos,
|
279
|
+
ypos,
|
280
|
+
remaining_width,
|
281
|
+
height,
|
282
|
+
DiInt(
|
283
|
+
bbox.top,
|
284
|
+
max(0, bbox.left - write_position.xpos - xpos),
|
285
|
+
bbox.bottom,
|
286
|
+
max(
|
287
|
+
bbox.right,
|
288
|
+
write_position.xpos + write_position.width - xpos - s,
|
289
|
+
),
|
290
|
+
),
|
291
|
+
),
|
292
|
+
style,
|
293
|
+
erase_bg,
|
294
|
+
z_index,
|
295
|
+
)
|
296
|
+
|
297
|
+
|
298
|
+
class Window(containers.Window):
|
299
|
+
"""Container that holds a control."""
|
300
|
+
|
301
|
+
def write_to_screen(
|
302
|
+
self,
|
303
|
+
screen: Screen,
|
304
|
+
mouse_handlers: MouseHandlers,
|
305
|
+
write_position: WritePosition,
|
306
|
+
parent_style: str,
|
307
|
+
erase_bg: bool,
|
308
|
+
z_index: int | None,
|
309
|
+
) -> None:
|
310
|
+
"""Write window to screen."""
|
311
|
+
assert isinstance(write_position, BoundedWritePosition)
|
312
|
+
# If dont_extend_width/height was given, then reduce width/height in
|
313
|
+
# WritePosition, if the parent wanted us to paint in a bigger area.
|
314
|
+
# (This happens if this window is bundled with another window in a
|
315
|
+
# HSplit/VSplit, but with different size requirements.)
|
316
|
+
write_position = BoundedWritePosition(
|
317
|
+
xpos=write_position.xpos,
|
318
|
+
ypos=write_position.ypos,
|
319
|
+
width=write_position.width,
|
320
|
+
height=write_position.height,
|
321
|
+
bbox=write_position.bbox,
|
322
|
+
)
|
323
|
+
|
324
|
+
if self.dont_extend_width():
|
325
|
+
write_position.width = min(
|
326
|
+
write_position.width,
|
327
|
+
self.preferred_width(write_position.width).preferred,
|
328
|
+
)
|
329
|
+
|
330
|
+
if self.dont_extend_height():
|
331
|
+
write_position.height = min(
|
332
|
+
write_position.height,
|
333
|
+
self.preferred_height(
|
334
|
+
write_position.width, write_position.height
|
335
|
+
).preferred,
|
336
|
+
)
|
337
|
+
|
338
|
+
# Draw
|
339
|
+
z_index = z_index if self.z_index is None else self.z_index
|
340
|
+
|
341
|
+
draw_func = partial(
|
342
|
+
self._write_to_screen_at_index,
|
343
|
+
screen,
|
344
|
+
mouse_handlers,
|
345
|
+
write_position,
|
346
|
+
parent_style,
|
347
|
+
erase_bg,
|
348
|
+
)
|
349
|
+
|
350
|
+
if z_index is None or z_index <= 0:
|
351
|
+
# When no z_index is given, draw right away.
|
352
|
+
draw_func()
|
353
|
+
else:
|
354
|
+
# Otherwise, postpone.
|
355
|
+
screen.draw_with_z_index(z_index=z_index, draw_func=draw_func)
|
356
|
+
|
357
|
+
def _write_to_screen_at_index(
|
358
|
+
self,
|
359
|
+
screen: Screen,
|
360
|
+
mouse_handlers: MouseHandlers,
|
361
|
+
write_position: WritePosition,
|
362
|
+
parent_style: str,
|
363
|
+
erase_bg: bool,
|
364
|
+
) -> None:
|
365
|
+
assert isinstance(write_position, BoundedWritePosition)
|
366
|
+
# Don't bother writing invisible windows.
|
367
|
+
# (We save some time, but also avoid applying last-line styling.)
|
368
|
+
if write_position.height <= 0 or write_position.width <= 0:
|
369
|
+
return
|
370
|
+
|
371
|
+
# Calculate margin sizes.
|
372
|
+
left_margin_widths = [self._get_margin_width(m) for m in self.left_margins]
|
373
|
+
right_margin_widths = [self._get_margin_width(m) for m in self.right_margins]
|
374
|
+
total_margin_width = sum(left_margin_widths + right_margin_widths)
|
375
|
+
|
376
|
+
# Render UserControl.
|
377
|
+
ui_content = self.content.create_content(
|
378
|
+
write_position.width - total_margin_width, write_position.height
|
379
|
+
)
|
380
|
+
assert isinstance(ui_content, UIContent)
|
381
|
+
|
382
|
+
# Scroll content.
|
383
|
+
wrap_lines = self.wrap_lines()
|
384
|
+
self._scroll(
|
385
|
+
ui_content, write_position.width - total_margin_width, write_position.height
|
386
|
+
)
|
387
|
+
|
388
|
+
# Erase background and fill with `char`.
|
389
|
+
self._fill_bg(screen, write_position, erase_bg)
|
390
|
+
|
391
|
+
# Resolve `align` attribute.
|
392
|
+
align = self.align() if callable(self.align) else self.align
|
393
|
+
|
394
|
+
# Write body
|
395
|
+
bbox = write_position.bbox
|
396
|
+
visible_line_to_row_col, rowcol_to_yx = self._copy_body(
|
397
|
+
ui_content,
|
398
|
+
screen,
|
399
|
+
write_position,
|
400
|
+
sum(left_margin_widths),
|
401
|
+
write_position.width - total_margin_width - bbox.left - bbox.right,
|
402
|
+
self.vertical_scroll,
|
403
|
+
self.horizontal_scroll,
|
404
|
+
wrap_lines=wrap_lines,
|
405
|
+
highlight_lines=True,
|
406
|
+
vertical_scroll_2=self.vertical_scroll_2,
|
407
|
+
always_hide_cursor=self.always_hide_cursor(),
|
408
|
+
has_focus=get_app().layout.current_control == self.content,
|
409
|
+
align=align,
|
410
|
+
get_line_prefix=self.get_line_prefix,
|
411
|
+
)
|
412
|
+
|
413
|
+
# Remember render info. (Set before generating the margins. They need this.)
|
414
|
+
x_offset = write_position.xpos + sum(left_margin_widths)
|
415
|
+
y_offset = write_position.ypos
|
416
|
+
|
417
|
+
render_info = WindowRenderInfo(
|
418
|
+
window=self,
|
419
|
+
ui_content=ui_content,
|
420
|
+
horizontal_scroll=self.horizontal_scroll,
|
421
|
+
vertical_scroll=self.vertical_scroll,
|
422
|
+
window_width=write_position.width - total_margin_width,
|
423
|
+
window_height=write_position.height,
|
424
|
+
configured_scroll_offsets=self.scroll_offsets,
|
425
|
+
visible_line_to_row_col=visible_line_to_row_col,
|
426
|
+
rowcol_to_yx=rowcol_to_yx,
|
427
|
+
x_offset=x_offset,
|
428
|
+
y_offset=y_offset,
|
429
|
+
wrap_lines=wrap_lines,
|
430
|
+
)
|
431
|
+
self.render_info = render_info
|
432
|
+
|
433
|
+
# Set mouse handlers.
|
434
|
+
def mouse_handler(mouse_event: MouseEvent) -> NotImplementedOrNone:
|
435
|
+
"""Turn screen coordinates into line coordinates."""
|
436
|
+
# Don't handle mouse events outside of the current modal part of
|
437
|
+
# the UI.
|
438
|
+
if self not in get_app().layout.walk_through_modal_area():
|
439
|
+
return NotImplemented
|
440
|
+
|
441
|
+
# Find row/col position first.
|
442
|
+
yx_to_rowcol = {v: k for k, v in rowcol_to_yx.items()}
|
443
|
+
y = mouse_event.position.y
|
444
|
+
x = mouse_event.position.x
|
445
|
+
|
446
|
+
# If clicked below the content area, look for a position in the
|
447
|
+
# last line instead.
|
448
|
+
max_y = write_position.ypos + len(visible_line_to_row_col) - 1
|
449
|
+
y = min(max_y, y)
|
450
|
+
result: NotImplementedOrNone
|
451
|
+
|
452
|
+
while x >= 0:
|
453
|
+
try:
|
454
|
+
row, col = yx_to_rowcol[y, x]
|
455
|
+
except KeyError:
|
456
|
+
# Try again. (When clicking on the right side of double
|
457
|
+
# width characters, or on the right side of the input.)
|
458
|
+
x -= 1
|
459
|
+
else:
|
460
|
+
# Found position, call handler of UIControl.
|
461
|
+
result = self.content.mouse_handler(
|
462
|
+
MouseEvent(
|
463
|
+
position=Point(x=col, y=row),
|
464
|
+
event_type=mouse_event.event_type,
|
465
|
+
button=mouse_event.button,
|
466
|
+
modifiers=mouse_event.modifiers,
|
467
|
+
)
|
468
|
+
)
|
469
|
+
break
|
470
|
+
else:
|
471
|
+
# nobreak.
|
472
|
+
# (No x/y coordinate found for the content. This happens in
|
473
|
+
# case of a DummyControl, that does not have any content.
|
474
|
+
# Report (0,0) instead.)
|
475
|
+
result = self.content.mouse_handler(
|
476
|
+
MouseEvent(
|
477
|
+
position=Point(x=0, y=0),
|
478
|
+
event_type=mouse_event.event_type,
|
479
|
+
button=mouse_event.button,
|
480
|
+
modifiers=mouse_event.modifiers,
|
481
|
+
)
|
482
|
+
)
|
483
|
+
|
484
|
+
# If it returns NotImplemented, handle it here.
|
485
|
+
if result == NotImplemented:
|
486
|
+
result = self._mouse_handler(mouse_event)
|
487
|
+
|
488
|
+
return result
|
489
|
+
|
490
|
+
mouse_handlers.set_mouse_handler_for_range(
|
491
|
+
x_min=write_position.xpos + max(sum(left_margin_widths), bbox.left),
|
492
|
+
x_max=write_position.xpos
|
493
|
+
+ write_position.width
|
494
|
+
- max(total_margin_width, bbox.right),
|
495
|
+
y_min=write_position.ypos + bbox.top,
|
496
|
+
y_max=write_position.ypos + write_position.height - bbox.bottom,
|
497
|
+
handler=mouse_handler,
|
498
|
+
)
|
499
|
+
|
500
|
+
# Render and copy margins.
|
501
|
+
move_x = 0
|
502
|
+
|
503
|
+
def render_margin(m: Margin, width: int) -> UIContent:
|
504
|
+
"""Render margin. Return `Screen`."""
|
505
|
+
# Retrieve margin fragments.
|
506
|
+
fragments = m.create_margin(render_info, width, write_position.height)
|
507
|
+
|
508
|
+
# Turn it into a UIContent object.
|
509
|
+
# already rendered those fragments using this size.)
|
510
|
+
return FormattedTextControl(fragments).create_content(
|
511
|
+
width + 1, write_position.height
|
512
|
+
)
|
513
|
+
|
514
|
+
for m, width in zip(self.left_margins, left_margin_widths):
|
515
|
+
if width > 0: # (ConditionalMargin returns a zero width. -- Don't render.)
|
516
|
+
# Create screen for margin.
|
517
|
+
margin_content = render_margin(m, width)
|
518
|
+
|
519
|
+
# Copy and shift X.
|
520
|
+
self._copy_margin(margin_content, screen, write_position, move_x, width)
|
521
|
+
move_x += width
|
522
|
+
|
523
|
+
move_x = write_position.width - sum(right_margin_widths)
|
524
|
+
|
525
|
+
for m, width in zip(self.right_margins, right_margin_widths):
|
526
|
+
# Create screen for margin.
|
527
|
+
margin_content = render_margin(m, width)
|
528
|
+
|
529
|
+
# Copy and shift X.
|
530
|
+
self._copy_margin(margin_content, screen, write_position, move_x, width)
|
531
|
+
move_x += width
|
532
|
+
|
533
|
+
# Apply 'self.style'
|
534
|
+
self._apply_style(screen, write_position, parent_style)
|
535
|
+
|
536
|
+
# Additionally apply style to line with cursor if it is visible
|
537
|
+
if ui_content.show_cursor and not self.always_hide_cursor():
|
538
|
+
_col, _row = ui_content.cursor_position
|
539
|
+
if cp_yx := rowcol_to_yx.get((_row, _col)):
|
540
|
+
self._apply_style(
|
541
|
+
screen,
|
542
|
+
BoundedWritePosition(
|
543
|
+
xpos=write_position.xpos,
|
544
|
+
ypos=cp_yx[0],
|
545
|
+
width=write_position.width,
|
546
|
+
height=1,
|
547
|
+
),
|
548
|
+
parent_style,
|
549
|
+
)
|
550
|
+
|
551
|
+
# Tell the screen that this user control has been painted at this
|
552
|
+
# position.
|
553
|
+
screen.visible_windows_to_write_positions[self] = write_position
|
554
|
+
|
555
|
+
def _copy_body(
|
556
|
+
self,
|
557
|
+
ui_content: UIContent,
|
558
|
+
new_screen: Screen,
|
559
|
+
write_position: WritePosition,
|
560
|
+
move_x: int,
|
561
|
+
width: int,
|
562
|
+
vertical_scroll: int = 0,
|
563
|
+
horizontal_scroll: int = 0,
|
564
|
+
wrap_lines: bool = False,
|
565
|
+
highlight_lines: bool = False,
|
566
|
+
vertical_scroll_2: int = 0,
|
567
|
+
always_hide_cursor: bool = False,
|
568
|
+
has_focus: bool = False,
|
569
|
+
align: WindowAlign = WindowAlign.LEFT,
|
570
|
+
get_line_prefix: Callable[[int, int], AnyFormattedText] | None = None,
|
571
|
+
) -> tuple[dict[int, tuple[int, int]], dict[tuple[int, int], tuple[int, int]]]:
|
572
|
+
"""Copy the UIContent into the output screen."""
|
573
|
+
assert isinstance(write_position, BoundedWritePosition)
|
574
|
+
xpos = write_position.xpos + move_x
|
575
|
+
ypos = write_position.ypos
|
576
|
+
line_count = ui_content.line_count
|
577
|
+
new_buffer = new_screen.data_buffer
|
578
|
+
empty_char = _CHAR_CACHE["", ""]
|
579
|
+
|
580
|
+
bbox = write_position.bbox
|
581
|
+
|
582
|
+
# Map visible line number to (row, col) of input.
|
583
|
+
# 'col' will always be zero if line wrapping is off.
|
584
|
+
visible_line_to_row_col: dict[int, tuple[int, int]] = {}
|
585
|
+
|
586
|
+
# Maps (row, col) from the input to (y, x) screen coordinates.
|
587
|
+
rowcol_to_yx: dict[tuple[int, int], tuple[int, int]] = {}
|
588
|
+
|
589
|
+
def copy_line(
|
590
|
+
line: StyleAndTextTuples,
|
591
|
+
lineno: int,
|
592
|
+
x: int,
|
593
|
+
y: int,
|
594
|
+
is_input: bool = False,
|
595
|
+
) -> tuple[int, int]:
|
596
|
+
"""Copy over a single line to the output screen."""
|
597
|
+
current_rowcol_to_yx = (
|
598
|
+
rowcol_to_yx if is_input else {}
|
599
|
+
) # Throwaway dictionary.
|
600
|
+
|
601
|
+
# Draw line prefix.
|
602
|
+
if is_input and get_line_prefix:
|
603
|
+
prompt = to_formatted_text(get_line_prefix(lineno, 0))
|
604
|
+
x, y = copy_line(prompt, lineno, x, y, is_input=False)
|
605
|
+
|
606
|
+
# Scroll horizontally.
|
607
|
+
skipped = 0 # Characters skipped because of horizontal scrolling.
|
608
|
+
if horizontal_scroll and is_input:
|
609
|
+
h_scroll = horizontal_scroll
|
610
|
+
line = explode_text_fragments(line)
|
611
|
+
while h_scroll > 0 and line:
|
612
|
+
h_scroll -= get_cwidth(line[0][1])
|
613
|
+
skipped += 1
|
614
|
+
del line[:1] # Remove first character.
|
615
|
+
|
616
|
+
x -= h_scroll # When scrolling over double width character,
|
617
|
+
# this can end up being negative.
|
618
|
+
|
619
|
+
# Align this line. (Note that this doesn't work well when we use
|
620
|
+
# get_line_prefix and that function returns variable width prefixes.)
|
621
|
+
if align == WindowAlign.CENTER:
|
622
|
+
line_width = fragment_list_width(line)
|
623
|
+
if line_width < width:
|
624
|
+
x += (width - line_width) // 2
|
625
|
+
elif align == WindowAlign.RIGHT:
|
626
|
+
line_width = fragment_list_width(line)
|
627
|
+
if line_width < width:
|
628
|
+
x += width - line_width
|
629
|
+
|
630
|
+
col = 0
|
631
|
+
wrap_count = 0
|
632
|
+
for style, text, *_ in line:
|
633
|
+
new_buffer_row = new_buffer[y + ypos]
|
634
|
+
|
635
|
+
# Remember raw VT escape sequences. (E.g. FinalTerm's
|
636
|
+
# escape sequences.)
|
637
|
+
if "[ZeroWidthEscape]" in style:
|
638
|
+
new_screen.zero_width_escapes[y + ypos][x + xpos] += text
|
639
|
+
continue
|
640
|
+
|
641
|
+
for c in text:
|
642
|
+
char = _CHAR_CACHE[c, style]
|
643
|
+
char_width = char.width
|
644
|
+
|
645
|
+
# Wrap when the line width is exceeded.
|
646
|
+
if wrap_lines and x + char_width > width:
|
647
|
+
visible_line_to_row_col[y + 1] = (
|
648
|
+
lineno,
|
649
|
+
visible_line_to_row_col[y][1] + x,
|
650
|
+
)
|
651
|
+
y += 1
|
652
|
+
wrap_count += 1
|
653
|
+
x = 0
|
654
|
+
|
655
|
+
# Insert line prefix (continuation prompt).
|
656
|
+
if is_input and get_line_prefix:
|
657
|
+
prompt = to_formatted_text(
|
658
|
+
get_line_prefix(lineno, wrap_count)
|
659
|
+
)
|
660
|
+
x, y = copy_line(prompt, lineno, x, y, is_input=False)
|
661
|
+
|
662
|
+
new_buffer_row = new_buffer[y + ypos]
|
663
|
+
|
664
|
+
if y >= write_position.height:
|
665
|
+
return x, y # Break out of all for loops.
|
666
|
+
|
667
|
+
# Set character in screen and shift 'x'.
|
668
|
+
if x >= 0 and y >= 0 and x < width:
|
669
|
+
new_buffer_row[x + xpos] = char
|
670
|
+
|
671
|
+
# When we print a multi width character, make sure
|
672
|
+
# to erase the neighbors positions in the screen.
|
673
|
+
# (The empty string if different from everything,
|
674
|
+
# so next redraw this cell will repaint anyway.)
|
675
|
+
if char_width > 1:
|
676
|
+
for i in range(1, char_width):
|
677
|
+
new_buffer_row[x + xpos + i] = empty_char
|
678
|
+
|
679
|
+
# If this is a zero width characters, then it's
|
680
|
+
# probably part of a decomposed unicode character.
|
681
|
+
# See: https://en.wikipedia.org/wiki/Unicode_equivalence
|
682
|
+
# Merge it in the previous cell.
|
683
|
+
elif char_width == 0:
|
684
|
+
# Handle all character widths. If the previous
|
685
|
+
# character is a multiwidth character, then
|
686
|
+
# merge it two positions back.
|
687
|
+
for pw in [2, 1]: # Previous character width.
|
688
|
+
if (
|
689
|
+
x - pw >= 0
|
690
|
+
and new_buffer_row[x + xpos - pw].width == pw
|
691
|
+
):
|
692
|
+
prev_char = new_buffer_row[x + xpos - pw]
|
693
|
+
char2 = _CHAR_CACHE[
|
694
|
+
prev_char.char + c, prev_char.style
|
695
|
+
]
|
696
|
+
new_buffer_row[x + xpos - pw] = char2
|
697
|
+
|
698
|
+
# Keep track of write position for each character.
|
699
|
+
current_rowcol_to_yx[lineno, col + skipped] = (
|
700
|
+
y + ypos,
|
701
|
+
x + xpos,
|
702
|
+
)
|
703
|
+
|
704
|
+
col += 1
|
705
|
+
x += char_width
|
706
|
+
return x, y
|
707
|
+
|
708
|
+
# Copy content.
|
709
|
+
y = -vertical_scroll_2
|
710
|
+
lineno = vertical_scroll
|
711
|
+
|
712
|
+
# Render lines down to the end of the visible region (or to the cursor
|
713
|
+
# position, whichever is lower)
|
714
|
+
cursor_visible = ui_content.show_cursor and not self.always_hide_cursor()
|
715
|
+
while (
|
716
|
+
y < write_position.height - bbox.bottom
|
717
|
+
or (cursor_visible and lineno <= ui_content.cursor_position.y)
|
718
|
+
) and lineno < line_count:
|
719
|
+
visible_line_to_row_col[y] = (lineno, horizontal_scroll)
|
720
|
+
|
721
|
+
# If lines are wrapped, we need to render all of them so we know how many
|
722
|
+
# rows each line occupies.
|
723
|
+
# Otherwise, we can skip rendering lines which are not visible.
|
724
|
+
# Also always render the line with the visible cursor so we know it's position
|
725
|
+
if (wrap_lines or bbox.top <= y) or (
|
726
|
+
cursor_visible and lineno == ui_content.cursor_position.y
|
727
|
+
):
|
728
|
+
# Take the next line and copy it in the real screen.
|
729
|
+
line = ui_content.get_line(lineno)
|
730
|
+
# Copy margin and actual line.
|
731
|
+
x = 0
|
732
|
+
x, y = copy_line(line, lineno, x, y, is_input=True)
|
733
|
+
|
734
|
+
lineno += 1
|
735
|
+
y += 1
|
736
|
+
|
737
|
+
def cursor_pos_to_screen_pos(row: int, col: int) -> Point:
|
738
|
+
"""Translate row/col from UIContent to real Screen coordinates."""
|
739
|
+
try:
|
740
|
+
y, x = rowcol_to_yx[row, col]
|
741
|
+
except KeyError:
|
742
|
+
# Normally this should never happen. (It is a bug, if it happens.)
|
743
|
+
# But to be sure, return (0, 0)
|
744
|
+
return Point(x=0, y=0)
|
745
|
+
|
746
|
+
# raise ValueError(
|
747
|
+
# 'Invalid position. row=%r col=%r, vertical_scroll=%r, '
|
748
|
+
# 'horizontal_scroll=%r, height=%r' %
|
749
|
+
# (row, col, vertical_scroll, horizontal_scroll, write_position.height))
|
750
|
+
else:
|
751
|
+
return Point(x=x, y=y)
|
752
|
+
|
753
|
+
# Set cursor and menu positions.
|
754
|
+
if ui_content.cursor_position:
|
755
|
+
screen_cursor_position = cursor_pos_to_screen_pos(
|
756
|
+
ui_content.cursor_position.y, ui_content.cursor_position.x
|
757
|
+
)
|
758
|
+
|
759
|
+
if has_focus:
|
760
|
+
new_screen.set_cursor_position(self, screen_cursor_position)
|
761
|
+
|
762
|
+
if always_hide_cursor:
|
763
|
+
new_screen.show_cursor = False
|
764
|
+
else:
|
765
|
+
new_screen.show_cursor = ui_content.show_cursor
|
766
|
+
|
767
|
+
self._highlight_digraph(new_screen)
|
768
|
+
|
769
|
+
if highlight_lines:
|
770
|
+
self._highlight_cursorlines(
|
771
|
+
new_screen,
|
772
|
+
screen_cursor_position,
|
773
|
+
xpos,
|
774
|
+
ypos,
|
775
|
+
width,
|
776
|
+
write_position.height,
|
777
|
+
)
|
778
|
+
|
779
|
+
# Draw input characters from the input processor queue.
|
780
|
+
if has_focus and ui_content.cursor_position:
|
781
|
+
self._show_key_processor_key_buffer(new_screen)
|
782
|
+
|
783
|
+
# Set menu position.
|
784
|
+
if ui_content.menu_position:
|
785
|
+
new_screen.set_menu_position(
|
786
|
+
self,
|
787
|
+
cursor_pos_to_screen_pos(
|
788
|
+
ui_content.menu_position.y, ui_content.menu_position.x
|
789
|
+
),
|
790
|
+
)
|
791
|
+
|
792
|
+
# Update output screen height.
|
793
|
+
new_screen.height = max(new_screen.height, ypos + write_position.height)
|
794
|
+
|
795
|
+
return visible_line_to_row_col, rowcol_to_yx
|
796
|
+
|
797
|
+
def _copy_margin(
|
798
|
+
self,
|
799
|
+
margin_content: UIContent,
|
800
|
+
new_screen: Screen,
|
801
|
+
write_position: WritePosition,
|
802
|
+
move_x: int,
|
803
|
+
width: int,
|
804
|
+
) -> None:
|
805
|
+
"""Copy characters from the margin screen to the real screen."""
|
806
|
+
assert isinstance(write_position, BoundedWritePosition)
|
807
|
+
xpos = write_position.xpos + move_x
|
808
|
+
ypos = write_position.ypos
|
809
|
+
wp_bbox = write_position.bbox
|
810
|
+
|
811
|
+
margin_write_position = BoundedWritePosition(
|
812
|
+
xpos, ypos, width, write_position.height, wp_bbox
|
813
|
+
)
|
814
|
+
self._copy_body(margin_content, new_screen, margin_write_position, 0, width)
|
815
|
+
|
816
|
+
def _fill_bg(
|
817
|
+
self, screen: Screen, write_position: WritePosition, erase_bg: bool
|
818
|
+
) -> None:
|
819
|
+
"""Erase/fill the background."""
|
820
|
+
assert isinstance(write_position, BoundedWritePosition)
|
821
|
+
char: str | None
|
822
|
+
char = self.char() if callable(self.char) else self.char
|
823
|
+
|
824
|
+
if erase_bg or char:
|
825
|
+
wp = write_position
|
826
|
+
char_obj = _CHAR_CACHE[char or " ", ""]
|
827
|
+
|
828
|
+
bbox = wp.bbox
|
829
|
+
for y in range(wp.ypos + bbox.top, wp.ypos + wp.height - bbox.bottom):
|
830
|
+
row = screen.data_buffer[y]
|
831
|
+
for x in range(wp.xpos + bbox.left, wp.xpos + wp.width - bbox.right):
|
832
|
+
row[x] = char_obj
|
833
|
+
|
834
|
+
def _apply_style(
|
835
|
+
self,
|
836
|
+
new_screen: Screen,
|
837
|
+
write_position: WritePosition,
|
838
|
+
parent_style: str,
|
839
|
+
) -> None:
|
840
|
+
# Apply `self.style`.
|
841
|
+
style = f"{parent_style} {to_str(self.style)}"
|
842
|
+
|
843
|
+
new_screen.fill_area(write_position, style=style, after=False)
|
844
|
+
|
845
|
+
# Apply the 'last-line' class to the last line of each Window. This can
|
846
|
+
# be used to apply an 'underline' to the user control.
|
847
|
+
if isinstance(write_position, BoundedWritePosition):
|
848
|
+
if write_position.bbox.bottom == 0:
|
849
|
+
wp = BoundedWritePosition(
|
850
|
+
write_position.xpos,
|
851
|
+
write_position.ypos + write_position.height - 1,
|
852
|
+
write_position.width,
|
853
|
+
1,
|
854
|
+
)
|
855
|
+
new_screen.fill_area(wp, "class:last-line", after=True)
|
856
|
+
else:
|
857
|
+
new_screen.fill_area(write_position, "class:last-line", after=True)
|
858
|
+
|
859
|
+
|
860
|
+
class FloatContainer(containers.FloatContainer):
|
861
|
+
"""A `FloatContainer` which uses :py`BoundedWritePosition`s."""
|
862
|
+
|
863
|
+
def _draw_float(
|
864
|
+
self,
|
865
|
+
fl: Float,
|
866
|
+
screen: Screen,
|
867
|
+
mouse_handlers: MouseHandlers,
|
868
|
+
write_position: WritePosition,
|
869
|
+
style: str,
|
870
|
+
erase_bg: bool,
|
871
|
+
z_index: int | None,
|
872
|
+
) -> None:
|
873
|
+
"""Draw a single Float."""
|
874
|
+
# When a menu_position was given, use this instead of the cursor
|
875
|
+
# position. (These cursor positions are absolute, translate again
|
876
|
+
# relative to the write_position.)
|
877
|
+
# Note: This should be inside the for-loop, because one float could
|
878
|
+
# set the cursor position to be used for the next one.
|
879
|
+
cpos = screen.get_menu_position(
|
880
|
+
fl.attach_to_window or get_app().layout.current_window
|
881
|
+
)
|
882
|
+
cursor_position = Point(
|
883
|
+
x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos
|
884
|
+
)
|
885
|
+
|
886
|
+
fl_width = fl.get_width()
|
887
|
+
fl_height = fl.get_height()
|
888
|
+
width: int
|
889
|
+
height: int
|
890
|
+
xpos: int
|
891
|
+
ypos: int
|
892
|
+
|
893
|
+
# Left & width given.
|
894
|
+
if fl.left is not None and fl_width is not None:
|
895
|
+
xpos = fl.left
|
896
|
+
width = fl_width
|
897
|
+
# Left & right given -> calculate width.
|
898
|
+
elif fl.left is not None and fl.right is not None:
|
899
|
+
xpos = fl.left
|
900
|
+
width = write_position.width - fl.left - fl.right
|
901
|
+
# Width & right given -> calculate left.
|
902
|
+
elif fl_width is not None and fl.right is not None:
|
903
|
+
xpos = write_position.width - fl.right - fl_width
|
904
|
+
width = fl_width
|
905
|
+
# Near x position of cursor.
|
906
|
+
elif fl.xcursor:
|
907
|
+
if fl_width is None:
|
908
|
+
width = fl.content.preferred_width(write_position.width).preferred
|
909
|
+
width = min(write_position.width, width)
|
910
|
+
else:
|
911
|
+
width = fl_width
|
912
|
+
|
913
|
+
xpos = cursor_position.x
|
914
|
+
if xpos + width > write_position.width:
|
915
|
+
xpos = max(0, write_position.width - width)
|
916
|
+
# Only width given -> center horizontally.
|
917
|
+
elif fl_width:
|
918
|
+
xpos = int((write_position.width - fl_width) / 2)
|
919
|
+
width = fl_width
|
920
|
+
# Otherwise, take preferred width from float content.
|
921
|
+
else:
|
922
|
+
width = fl.content.preferred_width(write_position.width).preferred
|
923
|
+
|
924
|
+
if fl.left is not None:
|
925
|
+
xpos = fl.left
|
926
|
+
elif fl.right is not None:
|
927
|
+
xpos = max(0, write_position.width - width - fl.right)
|
928
|
+
else: # Center horizontally.
|
929
|
+
xpos = max(0, int((write_position.width - width) / 2))
|
930
|
+
|
931
|
+
# Trim.
|
932
|
+
width = min(width, write_position.width - xpos)
|
933
|
+
|
934
|
+
# Top & height given.
|
935
|
+
if fl.top is not None and fl_height is not None:
|
936
|
+
ypos = fl.top
|
937
|
+
height = fl_height
|
938
|
+
# Top & bottom given -> calculate height.
|
939
|
+
elif fl.top is not None and fl.bottom is not None:
|
940
|
+
ypos = fl.top
|
941
|
+
height = write_position.height - fl.top - fl.bottom
|
942
|
+
# Height & bottom given -> calculate top.
|
943
|
+
elif fl_height is not None and fl.bottom is not None:
|
944
|
+
ypos = write_position.height - fl_height - fl.bottom
|
945
|
+
height = fl_height
|
946
|
+
# Near cursor.
|
947
|
+
elif fl.ycursor:
|
948
|
+
ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1)
|
949
|
+
|
950
|
+
if fl_height is None:
|
951
|
+
height = fl.content.preferred_height(
|
952
|
+
width, write_position.height
|
953
|
+
).preferred
|
954
|
+
else:
|
955
|
+
height = fl_height
|
956
|
+
|
957
|
+
# Reduce height if not enough space. (We can use the height
|
958
|
+
# when the content requires it.)
|
959
|
+
if height > write_position.height - ypos:
|
960
|
+
if write_position.height - ypos + 1 >= ypos:
|
961
|
+
# When the space below the cursor is more than
|
962
|
+
# the space above, just reduce the height.
|
963
|
+
height = write_position.height - ypos
|
964
|
+
else:
|
965
|
+
# Otherwise, fit the float above the cursor.
|
966
|
+
height = min(height, cursor_position.y)
|
967
|
+
ypos = cursor_position.y - height
|
968
|
+
|
969
|
+
# Only height given -> center vertically.
|
970
|
+
elif fl_height:
|
971
|
+
ypos = int((write_position.height - fl_height) / 2)
|
972
|
+
height = fl_height
|
973
|
+
# Otherwise, take preferred height from content.
|
974
|
+
else:
|
975
|
+
height = fl.content.preferred_height(width, write_position.height).preferred
|
976
|
+
|
977
|
+
if fl.top is not None:
|
978
|
+
ypos = fl.top
|
979
|
+
elif fl.bottom is not None:
|
980
|
+
ypos = max(0, write_position.height - height - fl.bottom)
|
981
|
+
else: # Center vertically.
|
982
|
+
ypos = max(0, int((write_position.height - height) / 2))
|
983
|
+
|
984
|
+
# Trim.
|
985
|
+
height = min(height, write_position.height - ypos)
|
986
|
+
|
987
|
+
# Write float.
|
988
|
+
# (xpos and ypos can be negative: a float can be partially visible.)
|
989
|
+
if height > 0 and width > 0:
|
990
|
+
wp = BoundedWritePosition(
|
991
|
+
xpos=xpos + write_position.xpos,
|
992
|
+
ypos=ypos + write_position.ypos,
|
993
|
+
width=width,
|
994
|
+
height=height,
|
995
|
+
)
|
996
|
+
|
997
|
+
if not fl.hide_when_covering_content or self._area_is_empty(screen, wp):
|
998
|
+
fl.content.write_to_screen(
|
999
|
+
screen,
|
1000
|
+
mouse_handlers,
|
1001
|
+
wp,
|
1002
|
+
style,
|
1003
|
+
erase_bg=not fl.transparent(),
|
1004
|
+
z_index=z_index,
|
1005
|
+
)
|
1006
|
+
|
1007
|
+
|
1008
|
+
containers.HSplit = HSplit # type: ignore[misc]
|
1009
|
+
containers.VSplit = VSplit # type: ignore[misc]
|
1010
|
+
containers.Window = Window # type: ignore[misc]
|
1011
|
+
containers.FloatContainer = FloatContainer # type: ignore[misc]
|