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
euporie/core/widgets/decor.py
CHANGED
@@ -5,207 +5,34 @@ from __future__ import annotations
|
|
5
5
|
import logging
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
-
from prompt_toolkit.
|
9
|
-
from prompt_toolkit.filters import has_focus, to_filter
|
8
|
+
from prompt_toolkit.filters import to_filter
|
10
9
|
from prompt_toolkit.layout.containers import (
|
11
10
|
ConditionalContainer,
|
12
|
-
Container,
|
13
11
|
DynamicContainer,
|
14
12
|
Float,
|
15
13
|
FloatContainer,
|
16
|
-
HSplit,
|
17
|
-
VSplit,
|
18
|
-
Window,
|
19
|
-
to_container,
|
20
14
|
)
|
21
|
-
from prompt_toolkit.layout.dimension import Dimension
|
22
|
-
from prompt_toolkit.layout.screen import Char, Screen, WritePosition
|
23
|
-
from prompt_toolkit.mouse_events import MouseEventType
|
24
15
|
|
25
16
|
from euporie.core.border import ThinLine
|
26
17
|
from euporie.core.config import add_setting
|
27
18
|
from euporie.core.current import get_app
|
28
19
|
from euporie.core.data_structures import DiBool
|
29
|
-
from euporie.core.
|
20
|
+
from euporie.core.layout.containers import HSplit, VSplit, Window
|
21
|
+
from euporie.core.layout.decor import DropShadow
|
30
22
|
|
31
23
|
if TYPE_CHECKING:
|
32
24
|
from typing import Callable
|
33
25
|
|
34
|
-
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
35
26
|
from prompt_toolkit.layout.containers import AnyContainer
|
36
|
-
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
|
37
27
|
from prompt_toolkit.mouse_events import MouseEvent
|
38
28
|
|
39
29
|
from euporie.core.border import GridStyle
|
40
|
-
from euporie.core.style import ColorPalette
|
41
30
|
|
42
31
|
MouseHandler = Callable[[MouseEvent], object]
|
43
32
|
|
44
33
|
log = logging.getLogger(__name__)
|
45
34
|
|
46
35
|
|
47
|
-
class Line(Container):
|
48
|
-
"""Draw a horizontal or vertical line."""
|
49
|
-
|
50
|
-
def __init__(
|
51
|
-
self,
|
52
|
-
char: str | None = None,
|
53
|
-
width: int | None = None,
|
54
|
-
height: int | None = None,
|
55
|
-
collapse: bool = False,
|
56
|
-
style: str = "class:grid-line",
|
57
|
-
) -> None:
|
58
|
-
"""Initialize a grid line.
|
59
|
-
|
60
|
-
Args:
|
61
|
-
char: The character to draw. If unset, the relevant character from
|
62
|
-
:py:class:`euporie.core.box.grid` is used
|
63
|
-
width: The length of the line. If specified, the line will be horizontal
|
64
|
-
height: The height of the line. If specified, the line will be vertical
|
65
|
-
collapse: Whether to hide the line when there is not enough space
|
66
|
-
style: Style to apply to the line
|
67
|
-
|
68
|
-
Raises:
|
69
|
-
ValueError: If both width and height are specified. A line must only have a
|
70
|
-
single dimension.
|
71
|
-
|
72
|
-
"""
|
73
|
-
if width and height:
|
74
|
-
raise ValueError("Only one of `width` or `height` must be set")
|
75
|
-
self.width = width
|
76
|
-
self.height = height
|
77
|
-
if char is None:
|
78
|
-
char = ThinLine.grid.VERTICAL if width else ThinLine.grid.HORIZONTAL
|
79
|
-
self.char = Char(char, style)
|
80
|
-
self.collapse = collapse
|
81
|
-
|
82
|
-
def reset(self) -> None:
|
83
|
-
"""Reet the state of the line. Does nothing."""
|
84
|
-
|
85
|
-
def preferred_width(self, max_available_width: int) -> Dimension:
|
86
|
-
"""Return the preferred width of the line."""
|
87
|
-
return Dimension(min=int(not self.collapse), max=self.width)
|
88
|
-
|
89
|
-
def preferred_height(self, width: int, max_available_height: int) -> Dimension:
|
90
|
-
"""Return the preferred height of the line."""
|
91
|
-
return Dimension(min=int(not self.collapse), max=self.height)
|
92
|
-
|
93
|
-
def write_to_screen(
|
94
|
-
self,
|
95
|
-
screen: Screen,
|
96
|
-
mouse_handlers: MouseHandlers,
|
97
|
-
write_position: WritePosition,
|
98
|
-
parent_style: str,
|
99
|
-
erase_bg: bool,
|
100
|
-
z_index: int | None,
|
101
|
-
) -> None:
|
102
|
-
"""Draw a continuous line in the ``write_position`` area.
|
103
|
-
|
104
|
-
Args:
|
105
|
-
screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which
|
106
|
-
the output has to be written.
|
107
|
-
mouse_handlers: :class:`prompt_toolkit.layout.mouse_handlers.MouseHandlers`.
|
108
|
-
write_position: A :class:`prompt_toolkit.layout.screen.WritePosition` object
|
109
|
-
defining where this container should be drawn.
|
110
|
-
erase_bg: If true, the background will be erased prior to drawing.
|
111
|
-
parent_style: Style string to pass to the :class:`.Window` object. This will
|
112
|
-
be applied to all content of the windows. :class:`.VSplit` and
|
113
|
-
:class:`prompt_toolkit.layout.containers.HSplit` can use it to pass
|
114
|
-
their style down to the windows that they contain.
|
115
|
-
z_index: Used for propagating z_index from parent to child.
|
116
|
-
|
117
|
-
"""
|
118
|
-
ypos = write_position.ypos
|
119
|
-
xpos = write_position.xpos
|
120
|
-
|
121
|
-
for y in range(ypos, ypos + write_position.height):
|
122
|
-
row = screen.data_buffer[y]
|
123
|
-
for x in range(xpos, xpos + write_position.width):
|
124
|
-
row[x] = self.char
|
125
|
-
|
126
|
-
def get_children(self) -> list:
|
127
|
-
"""Return an empty list of the container's children."""
|
128
|
-
return []
|
129
|
-
|
130
|
-
|
131
|
-
class Pattern(Container):
|
132
|
-
"""Fill an area with a repeating background pattern."""
|
133
|
-
|
134
|
-
def __init__(
|
135
|
-
self,
|
136
|
-
char: str | Callable[[], str],
|
137
|
-
pattern: int | Callable[[], int] = 1,
|
138
|
-
) -> None:
|
139
|
-
"""Initialize the :class:`Pattern`."""
|
140
|
-
self.bg = Char(" ", "class:pattern")
|
141
|
-
self.char = char
|
142
|
-
self.pattern = pattern
|
143
|
-
|
144
|
-
def reset(self) -> None:
|
145
|
-
"""Reet the pattern. Does nothing."""
|
146
|
-
|
147
|
-
def preferred_width(self, max_available_width: int) -> Dimension:
|
148
|
-
"""Return an empty dimension (expand to available width)."""
|
149
|
-
return Dimension()
|
150
|
-
|
151
|
-
def preferred_height(self, width: int, max_available_height: int) -> Dimension:
|
152
|
-
"""Return an empty dimension (expand to available height)."""
|
153
|
-
return Dimension()
|
154
|
-
|
155
|
-
def write_to_screen(
|
156
|
-
self,
|
157
|
-
screen: Screen,
|
158
|
-
mouse_handlers: MouseHandlers,
|
159
|
-
write_position: WritePosition,
|
160
|
-
parent_style: str,
|
161
|
-
erase_bg: bool,
|
162
|
-
z_index: int | None,
|
163
|
-
) -> None:
|
164
|
-
"""Fill the whole area of write_position with a pattern.
|
165
|
-
|
166
|
-
Args:
|
167
|
-
screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which
|
168
|
-
the output has to be written.
|
169
|
-
mouse_handlers: :class:`prompt_toolkit.layout.mouse_handlers.MouseHandlers`.
|
170
|
-
write_position: A :class:`prompt_toolkit.layout.screen.WritePosition` object
|
171
|
-
defining where this container should be drawn.
|
172
|
-
erase_bg: If true, the background will be erased prior to drawing.
|
173
|
-
parent_style: Style string to pass to the :class:`.Window` object. This will
|
174
|
-
be applied to all content of the windows. :class:`.VSplit` and
|
175
|
-
:class:`prompt_toolkit.layout.containers.HSplit` can use it to pass
|
176
|
-
their style down to the windows that they contain.
|
177
|
-
z_index: Used for propagating z_index from parent to child.
|
178
|
-
|
179
|
-
"""
|
180
|
-
bg = self.bg
|
181
|
-
pattern = self.pattern() if callable(self.pattern) else self.pattern
|
182
|
-
if callable(self.char):
|
183
|
-
char = Char(self.char(), "class:pattern")
|
184
|
-
else:
|
185
|
-
char = Char(self.char, "class:pattern")
|
186
|
-
|
187
|
-
ypos = write_position.ypos
|
188
|
-
xpos = write_position.xpos
|
189
|
-
|
190
|
-
for y in range(ypos, ypos + write_position.height):
|
191
|
-
row = screen.data_buffer[y]
|
192
|
-
for x in range(xpos, xpos + write_position.width):
|
193
|
-
if (
|
194
|
-
(pattern == 1)
|
195
|
-
or (pattern == 2 and (x + y) % 2 == 0)
|
196
|
-
or (pattern == 3 and (x + 2 * y) % 4 == 0)
|
197
|
-
or (pattern == 4 and (x + y) % 3 == 0)
|
198
|
-
or (pattern == 5 and ((x + y % 2 * 3) % 6) % 4 == 0)
|
199
|
-
):
|
200
|
-
row[x] = char
|
201
|
-
else:
|
202
|
-
row[x] = bg
|
203
|
-
|
204
|
-
def get_children(self) -> list:
|
205
|
-
"""Return an empty list of the container's children."""
|
206
|
-
return []
|
207
|
-
|
208
|
-
|
209
36
|
class Border:
|
210
37
|
"""Draw a border around any container."""
|
211
38
|
|
@@ -353,189 +180,6 @@ class Border:
|
|
353
180
|
return self.container
|
354
181
|
|
355
182
|
|
356
|
-
class FocusedStyle(Container):
|
357
|
-
"""Apply a style to child containers when focused or hovered."""
|
358
|
-
|
359
|
-
def __init__(
|
360
|
-
self,
|
361
|
-
body: AnyContainer,
|
362
|
-
style_focus: str | Callable[[], str] = "class:focused",
|
363
|
-
style_hover: str | Callable[[], str] = "",
|
364
|
-
) -> None:
|
365
|
-
"""Create a new instance of the widget.
|
366
|
-
|
367
|
-
Args:
|
368
|
-
body: The container to act on
|
369
|
-
style_focus: The style to apply when the body has focus
|
370
|
-
style_hover: The style to apply when the body is hovered
|
371
|
-
"""
|
372
|
-
self.body = body
|
373
|
-
self.style_focus = style_focus
|
374
|
-
self.style_hover = style_hover
|
375
|
-
self.hover = False
|
376
|
-
self.has_focus = has_focus(self.body)
|
377
|
-
|
378
|
-
def reset(self) -> None:
|
379
|
-
"""Reet the wrapped container."""
|
380
|
-
to_container(self.body).reset()
|
381
|
-
|
382
|
-
def preferred_width(self, max_available_width: int) -> Dimension:
|
383
|
-
"""Return the wrapped container's preferred width."""
|
384
|
-
return to_container(self.body).preferred_width(max_available_width)
|
385
|
-
|
386
|
-
def preferred_height(self, width: int, max_available_height: int) -> Dimension:
|
387
|
-
"""Return the wrapped container's preferred height."""
|
388
|
-
return to_container(self.body).preferred_height(width, max_available_height)
|
389
|
-
|
390
|
-
def write_to_screen(
|
391
|
-
self,
|
392
|
-
screen: Screen,
|
393
|
-
mouse_handlers: MouseHandlers,
|
394
|
-
write_position: WritePosition,
|
395
|
-
parent_style: str,
|
396
|
-
erase_bg: bool,
|
397
|
-
z_index: int | None,
|
398
|
-
) -> None:
|
399
|
-
"""Draw the wrapped container with the additional style."""
|
400
|
-
to_container(self.body).write_to_screen(
|
401
|
-
screen,
|
402
|
-
mouse_handlers,
|
403
|
-
write_position,
|
404
|
-
f"{parent_style} {self.get_style()}",
|
405
|
-
erase_bg,
|
406
|
-
z_index,
|
407
|
-
)
|
408
|
-
|
409
|
-
if self.style_hover:
|
410
|
-
x_min = write_position.xpos
|
411
|
-
x_max = x_min + write_position.width
|
412
|
-
y_min = write_position.ypos
|
413
|
-
y_max = y_min + write_position.height
|
414
|
-
|
415
|
-
# Wrap mouse handlers to add "hover" class on hover
|
416
|
-
def _wrap_mouse_handler(handler: Callable) -> MouseHandler:
|
417
|
-
def wrapped_mouse_handler(
|
418
|
-
mouse_event: MouseEvent,
|
419
|
-
) -> NotImplementedOrNone:
|
420
|
-
result = handler(mouse_event)
|
421
|
-
|
422
|
-
if mouse_event.event_type == MouseEventType.MOUSE_MOVE:
|
423
|
-
app = get_app()
|
424
|
-
if (
|
425
|
-
mouse_event.position.x,
|
426
|
-
mouse_event.position.y,
|
427
|
-
) == app.mouse_position:
|
428
|
-
app.mouse_limits = write_position
|
429
|
-
self.hover = True
|
430
|
-
else:
|
431
|
-
app.mouse_limits = None
|
432
|
-
self.hover = False
|
433
|
-
result = None
|
434
|
-
return result
|
435
|
-
|
436
|
-
return wrapped_mouse_handler
|
437
|
-
|
438
|
-
mouse_handler_wrappers: FastDictCache[
|
439
|
-
tuple[Callable], MouseHandler
|
440
|
-
] = FastDictCache(get_value=_wrap_mouse_handler)
|
441
|
-
for y in range(y_min, y_max):
|
442
|
-
row = mouse_handlers.mouse_handlers[y]
|
443
|
-
for x in range(x_min, x_max):
|
444
|
-
row[x] = mouse_handler_wrappers[(row[x],)]
|
445
|
-
|
446
|
-
def get_style(self) -> str:
|
447
|
-
"""Determine the style to apply depending on the focus status."""
|
448
|
-
style = ""
|
449
|
-
if self.has_focus():
|
450
|
-
style += (
|
451
|
-
self.style_focus() if callable(self.style_focus) else self.style_focus
|
452
|
-
)
|
453
|
-
if self.hover:
|
454
|
-
style += " " + (
|
455
|
-
self.style_hover() if callable(self.style_hover) else self.style_hover
|
456
|
-
)
|
457
|
-
return style
|
458
|
-
|
459
|
-
def get_children(self) -> list[Container]:
|
460
|
-
"""Return the list of child :class:`.Container` objects."""
|
461
|
-
return [to_container(self.body)]
|
462
|
-
|
463
|
-
|
464
|
-
class DropShadow(Container):
|
465
|
-
"""A transparent container which makes the background darker."""
|
466
|
-
|
467
|
-
def __init__(self, amount: float = 0.5) -> None:
|
468
|
-
"""Create a new instance."""
|
469
|
-
self.amount = amount
|
470
|
-
self.renderer = get_app().renderer
|
471
|
-
|
472
|
-
@property
|
473
|
-
def cp(self) -> ColorPalette:
|
474
|
-
"""Get the current app's current color palette."""
|
475
|
-
return get_app().color_palette
|
476
|
-
|
477
|
-
def reset(self) -> None:
|
478
|
-
"""Reet the wrapped container - here, do nothing."""
|
479
|
-
|
480
|
-
def preferred_width(self, max_available_width: int) -> Dimension:
|
481
|
-
"""Return the wrapped container's preferred width."""
|
482
|
-
return Dimension(weight=1)
|
483
|
-
|
484
|
-
def preferred_height(self, width: int, max_available_height: int) -> Dimension:
|
485
|
-
"""Return the wrapped container's preferred height."""
|
486
|
-
return Dimension(weight=1)
|
487
|
-
|
488
|
-
def write_to_screen(
|
489
|
-
self,
|
490
|
-
screen: Screen,
|
491
|
-
mouse_handlers: MouseHandlers,
|
492
|
-
write_position: WritePosition,
|
493
|
-
parent_style: str,
|
494
|
-
erase_bg: bool,
|
495
|
-
z_index: int | None,
|
496
|
-
) -> None:
|
497
|
-
"""Draw the wrapped container with the additional style."""
|
498
|
-
attr_cache = self.renderer._attrs_for_style
|
499
|
-
if attr_cache is not None:
|
500
|
-
ypos = write_position.ypos
|
501
|
-
xpos = write_position.xpos
|
502
|
-
amount = self.amount
|
503
|
-
for y in range(ypos, ypos + write_position.height):
|
504
|
-
row = screen.data_buffer[y]
|
505
|
-
for x in range(xpos, xpos + write_position.width):
|
506
|
-
char = row[x]
|
507
|
-
style = char.style
|
508
|
-
attrs = attr_cache[style]
|
509
|
-
|
510
|
-
if not (fg := attrs.color) or fg == "default":
|
511
|
-
color = self.cp.fg
|
512
|
-
style += f" fg:{color.darker(amount)}"
|
513
|
-
else:
|
514
|
-
try:
|
515
|
-
color = ColorPaletteColor(fg)
|
516
|
-
except ValueError:
|
517
|
-
pass
|
518
|
-
else:
|
519
|
-
style += f" fg:{color.darker(amount)}"
|
520
|
-
|
521
|
-
if not (bg := attrs.bgcolor) or bg == "default":
|
522
|
-
color = self.cp.bg
|
523
|
-
style += f" bg:{color.darker(amount)}"
|
524
|
-
else:
|
525
|
-
try:
|
526
|
-
color = ColorPaletteColor(bg)
|
527
|
-
except ValueError:
|
528
|
-
pass
|
529
|
-
else:
|
530
|
-
style += f" bg:{color.darker(amount)}"
|
531
|
-
|
532
|
-
row[x] = Char(char=char.char, style=style)
|
533
|
-
|
534
|
-
def get_children(self) -> list[Container]:
|
535
|
-
"""Return an empty list of child :class:`.Container` objects."""
|
536
|
-
return []
|
537
|
-
|
538
|
-
|
539
183
|
class Shadow:
|
540
184
|
"""Draw a shadow underneath/behind this container.
|
541
185
|
|
euporie/core/widgets/dialog.py
CHANGED
@@ -27,15 +27,12 @@ from prompt_toolkit.layout.containers import (
|
|
27
27
|
ConditionalContainer,
|
28
28
|
DynamicContainer,
|
29
29
|
Float,
|
30
|
-
HSplit,
|
31
|
-
VSplit,
|
32
|
-
Window,
|
33
30
|
)
|
34
31
|
from prompt_toolkit.layout.controls import FormattedTextControl, UIContent, UIControl
|
35
32
|
from prompt_toolkit.layout.dimension import Dimension
|
36
33
|
from prompt_toolkit.layout.screen import WritePosition
|
37
34
|
from prompt_toolkit.mouse_events import MouseButton, MouseEventType
|
38
|
-
from prompt_toolkit.widgets.base import
|
35
|
+
from prompt_toolkit.widgets.base import Label
|
39
36
|
|
40
37
|
from euporie.core.app import get_app
|
41
38
|
from euporie.core.border import (
|
@@ -47,10 +44,13 @@ from euporie.core.commands import add_cmd
|
|
47
44
|
from euporie.core.filters import tab_has_focus
|
48
45
|
from euporie.core.ft.utils import FormattedTextAlign, align, lex
|
49
46
|
from euporie.core.key_binding.registry import register_bindings
|
47
|
+
from euporie.core.layout.containers import HSplit, VSplit, Window
|
48
|
+
from euporie.core.layout.decor import FocusedStyle
|
50
49
|
from euporie.core.tabs.base import Tab
|
51
|
-
from euporie.core.widgets.decor import Border,
|
50
|
+
from euporie.core.widgets.decor import Border, Shadow
|
52
51
|
from euporie.core.widgets.file_browser import FileBrowser
|
53
52
|
from euporie.core.widgets.forms import Button, LabelledWidget, Select, Text
|
53
|
+
from euporie.core.widgets.layout import Box
|
54
54
|
|
55
55
|
if TYPE_CHECKING:
|
56
56
|
from typing import Any, Callable, Hashable
|
euporie/core/widgets/display.py
CHANGED
@@ -3,14 +3,15 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import logging
|
6
|
+
from functools import partial
|
6
7
|
from math import ceil
|
7
8
|
from typing import TYPE_CHECKING, cast
|
8
9
|
|
9
|
-
from prompt_toolkit.cache import FastDictCache
|
10
|
+
from prompt_toolkit.cache import FastDictCache, SimpleCache
|
10
11
|
from prompt_toolkit.data_structures import Point, Size
|
11
12
|
from prompt_toolkit.filters.utils import to_filter
|
12
13
|
from prompt_toolkit.formatted_text.utils import fragment_list_width, split_lines
|
13
|
-
from prompt_toolkit.layout.containers import ConditionalContainer
|
14
|
+
from prompt_toolkit.layout.containers import ConditionalContainer
|
14
15
|
from prompt_toolkit.layout.controls import GetLinePrefixCallable, UIContent, UIControl
|
15
16
|
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
16
17
|
from prompt_toolkit.utils import Event, to_str
|
@@ -23,15 +24,14 @@ from euporie.core.filters import (
|
|
23
24
|
scrollable,
|
24
25
|
)
|
25
26
|
from euporie.core.ft.utils import wrap
|
27
|
+
from euporie.core.graphics import GraphicProcessor
|
26
28
|
from euporie.core.key_binding.registry import (
|
27
29
|
load_registered_bindings,
|
28
30
|
register_bindings,
|
29
31
|
)
|
32
|
+
from euporie.core.layout.containers import VSplit, Window
|
30
33
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
31
34
|
from euporie.core.utils import run_in_thread_with_context
|
32
|
-
from euporie.core.widgets.graphics import (
|
33
|
-
GraphicProcessor,
|
34
|
-
)
|
35
35
|
|
36
36
|
if TYPE_CHECKING:
|
37
37
|
from typing import Any, Callable, Iterable
|
@@ -96,10 +96,17 @@ class DisplayControl(UIControl):
|
|
96
96
|
self.on_cursor_position_changed,
|
97
97
|
]
|
98
98
|
|
99
|
+
# Caches
|
100
|
+
self._content_cache: FastDictCache = FastDictCache(self.get_content, size=1000)
|
99
101
|
self._line_cache: FastDictCache[
|
100
102
|
tuple[Datum, int | None, int | None, bool], list[StyleAndTextTuples]
|
101
|
-
] = FastDictCache(get_value=self.get_lines, size=
|
102
|
-
self.
|
103
|
+
] = FastDictCache(get_value=self.get_lines, size=1000)
|
104
|
+
self._line_width_cache: SimpleCache[
|
105
|
+
tuple[Datum, int | None, int | None, bool, int], int
|
106
|
+
] = SimpleCache(maxsize=10_000)
|
107
|
+
self._max_line_width_cache: FastDictCache[
|
108
|
+
tuple[Datum, int | None, int | None, bool], int
|
109
|
+
] = FastDictCache(get_value=self.get_max_line_width, size=1000)
|
103
110
|
|
104
111
|
@property
|
105
112
|
def datum(self) -> Any:
|
@@ -157,6 +164,23 @@ class DisplayControl(UIControl):
|
|
157
164
|
lines.extend([[]] * max(0, height - len(lines)))
|
158
165
|
return lines
|
159
166
|
|
167
|
+
def get_max_line_width(
|
168
|
+
self,
|
169
|
+
datum: Datum,
|
170
|
+
width: int | None,
|
171
|
+
height: int | None,
|
172
|
+
wrap_lines: bool = False,
|
173
|
+
) -> int:
|
174
|
+
"""Get the maximum lines width for a given rendering."""
|
175
|
+
lines = self._line_cache[datum, width, height, wrap_lines]
|
176
|
+
return max(
|
177
|
+
self._line_width_cache.get(
|
178
|
+
(datum, width, height, wrap_lines, i),
|
179
|
+
partial(fragment_list_width, line),
|
180
|
+
)
|
181
|
+
for i, line in enumerate(lines)
|
182
|
+
)
|
183
|
+
|
160
184
|
def render(self) -> None:
|
161
185
|
"""Render the HTML DOM in a thread."""
|
162
186
|
datum = self.datum
|
@@ -167,7 +191,6 @@ class DisplayControl(UIControl):
|
|
167
191
|
rows = ceil(cols * aspect) if aspect else self.height
|
168
192
|
|
169
193
|
def _render() -> None:
|
170
|
-
# Potentially redirect url
|
171
194
|
self.lines = self._line_cache[datum, cols, rows, wrap_lines]
|
172
195
|
self.loading = False
|
173
196
|
self.resizing = False
|
@@ -189,11 +212,9 @@ class DisplayControl(UIControl):
|
|
189
212
|
max_cols, aspect = self.datum.cell_size()
|
190
213
|
if max_cols:
|
191
214
|
return min(max_cols, max_available_width)
|
192
|
-
|
215
|
+
return self._max_line_width_cache[
|
193
216
|
self.datum, max_available_width, None, self.wrap_lines()
|
194
217
|
]
|
195
|
-
return max(fragment_list_width(line) for line in self.lines)
|
196
|
-
return max_available_width
|
197
218
|
|
198
219
|
def preferred_height(
|
199
220
|
self,
|
@@ -206,7 +227,6 @@ class DisplayControl(UIControl):
|
|
206
227
|
max_cols, aspect = self.datum.cell_size()
|
207
228
|
if aspect:
|
208
229
|
return ceil(min(width, max_cols) * aspect)
|
209
|
-
|
210
230
|
self.lines = self._line_cache[self.datum, width, None, self.wrap_lines()]
|
211
231
|
return len(self.lines)
|
212
232
|
|
@@ -14,9 +14,6 @@ from prompt_toolkit.filters.utils import to_filter
|
|
14
14
|
from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase
|
15
15
|
from prompt_toolkit.layout.containers import (
|
16
16
|
ConditionalContainer,
|
17
|
-
HSplit,
|
18
|
-
VSplit,
|
19
|
-
Window,
|
20
17
|
)
|
21
18
|
from prompt_toolkit.layout.controls import UIContent, UIControl
|
22
19
|
from prompt_toolkit.layout.screen import WritePosition
|
@@ -27,8 +24,10 @@ from upath import UPath
|
|
27
24
|
from euporie.core.app import get_app
|
28
25
|
from euporie.core.border import InsetGrid
|
29
26
|
from euporie.core.config import add_setting
|
27
|
+
from euporie.core.layout.containers import HSplit, VSplit, Window
|
28
|
+
from euporie.core.layout.decor import FocusedStyle
|
30
29
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
31
|
-
from euporie.core.widgets.decor import Border
|
30
|
+
from euporie.core.widgets.decor import Border
|
32
31
|
from euporie.core.widgets.forms import Button, Text
|
33
32
|
|
34
33
|
if TYPE_CHECKING:
|
euporie/core/widgets/forms.py
CHANGED
@@ -44,7 +44,7 @@ from prompt_toolkit.layout.utils import explode_text_fragments
|
|
44
44
|
from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
|
45
45
|
from prompt_toolkit.utils import Event
|
46
46
|
from prompt_toolkit.validation import Validator
|
47
|
-
from prompt_toolkit.widgets.base import
|
47
|
+
from prompt_toolkit.widgets.base import TextArea
|
48
48
|
|
49
49
|
from euporie.core.border import InsetGrid
|
50
50
|
from euporie.core.current import get_app
|
@@ -52,7 +52,7 @@ from euporie.core.data_structures import DiBool
|
|
52
52
|
from euporie.core.ft.utils import FormattedTextAlign, align
|
53
53
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
54
54
|
from euporie.core.widgets.decor import Border, Shadow
|
55
|
-
from euporie.core.widgets.layout import ConditionalSplit
|
55
|
+
from euporie.core.widgets.layout import Box, ConditionalSplit
|
56
56
|
|
57
57
|
if TYPE_CHECKING:
|
58
58
|
from typing import Any, Callable, Sequence
|
@@ -240,7 +240,12 @@ class Button:
|
|
240
240
|
if mouse_event.button == MouseButton.LEFT:
|
241
241
|
if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
|
242
242
|
self.selected = True
|
243
|
-
|
243
|
+
# Set global mouse capture
|
244
|
+
if (
|
245
|
+
self.window is not None
|
246
|
+
and (render_info := self.window.render_info) is not None
|
247
|
+
and render_info._rowcol_to_yx
|
248
|
+
):
|
244
249
|
y_min, x_min = min(render_info._rowcol_to_yx.values())
|
245
250
|
y_max, x_max = max(render_info._rowcol_to_yx.values())
|
246
251
|
get_app().mouse_limits = WritePosition(
|
@@ -632,6 +637,19 @@ class Text:
|
|
632
637
|
key_bindings=self.text_area.control.key_bindings,
|
633
638
|
expand=expand,
|
634
639
|
)
|
640
|
+
window = self.text_area.window
|
641
|
+
self.text_area.window = Window(
|
642
|
+
height=window.height,
|
643
|
+
width=window.width,
|
644
|
+
dont_extend_height=window.dont_extend_height,
|
645
|
+
dont_extend_width=window.dont_extend_width,
|
646
|
+
content=self.text_area.control,
|
647
|
+
style=window.style,
|
648
|
+
wrap_lines=window.wrap_lines,
|
649
|
+
left_margins=window.left_margins,
|
650
|
+
right_margins=window.right_margins,
|
651
|
+
get_line_prefix=window.get_line_prefix,
|
652
|
+
)
|
635
653
|
self.text_area.window.content = self.text_area.control
|
636
654
|
|
637
655
|
if on_text_changed:
|
@@ -1568,18 +1586,21 @@ class ToggleButtons(SelectableWidget):
|
|
1568
1586
|
|
1569
1587
|
def load_container(self) -> AnyContainer:
|
1570
1588
|
"""Load the widget's container."""
|
1571
|
-
if self.
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1589
|
+
if self.options:
|
1590
|
+
if self.vertical():
|
1591
|
+
show_borders_values = [
|
1592
|
+
DiBool(False, True, False, True) for _ in self.options
|
1593
|
+
]
|
1594
|
+
show_borders_values[0] = show_borders_values[0]._replace(top=True)
|
1595
|
+
show_borders_values[-1] = show_borders_values[-1]._replace(bottom=True)
|
1596
|
+
else:
|
1597
|
+
show_borders_values = [
|
1598
|
+
DiBool(True, False, True, False) for _ in self.options
|
1599
|
+
]
|
1600
|
+
show_borders_values[0] = show_borders_values[0]._replace(left=True)
|
1601
|
+
show_borders_values[-1] = show_borders_values[-1]._replace(right=True)
|
1577
1602
|
else:
|
1578
|
-
show_borders_values = [
|
1579
|
-
DiBool(True, False, True, False) for _ in self.options
|
1580
|
-
]
|
1581
|
-
show_borders_values[0] = show_borders_values[0]._replace(left=True)
|
1582
|
-
show_borders_values[-1] = show_borders_values[-1]._replace(right=True)
|
1603
|
+
show_borders_values = []
|
1583
1604
|
self.buttons = [
|
1584
1605
|
ToggleButton(
|
1585
1606
|
text=label,
|
@@ -1825,6 +1846,7 @@ class SliderControl(UIControl):
|
|
1825
1846
|
if (
|
1826
1847
|
self.window is not None
|
1827
1848
|
and (render_info := self.window.render_info) is not None
|
1849
|
+
and render_info._rowcol_to_yx
|
1828
1850
|
):
|
1829
1851
|
y_min, x_min = min(render_info._rowcol_to_yx.values())
|
1830
1852
|
y_max, x_max = max(render_info._rowcol_to_yx.values())
|