plesty.lib.ui.qt

PySide6 backend of the PLESTY shell.

Importing this package requires the gui extra (uv add "plesty-lib[gui]"): PySide6 and pyqtgraph. Everything above it — Panel, Theme, MonitorPanel — imports without a GUI installed, so headless hosts and CI keep working.

Submodules

Classes

PlotRenderer

Draw target that renders a monitor's traces into a pyqtgraph widget.

Recorder

Capture a window while active; encode a video file on stop.

DockPanel

One sub-window of the shell, wrapping a Panel.

PanelTitleBar

Title bar of a sub-window: state dot, title, live status, controls.

Shell

Home window hosting every panel as a rearrangeable sub-window.

Functions

application(→ PySide6.QtWidgets.QApplication)

Return the running Qt application, creating it when there is none.

build_shell(→ plesty.lib.ui.qt.shell.Shell)

Create a shell hosting panels, without starting the event loop.

run(→ int)

Open a shell on panels and run it until the window is closed.

Package Contents

plesty.lib.ui.qt.application(name: str = 'PLESTY') PySide6.QtWidgets.QApplication

Return the running Qt application, creating it when there is none.

Parameters:

name (str) – Application name, used by the window manager.

Returns:

The QApplication instance.

Return type:

PySide6.QtWidgets.QApplication

plesty.lib.ui.qt.build_shell(panels: Sequence[plesty.lib.ui.panel.Panel], *, title: str = 'PLESTY', theme: plesty.lib.ui.theme.Theme | None = None, interval_ms: int = 500, layout_file: str | pathlib.Path | None = None, remember_layout: bool = True) plesty.lib.ui.qt.shell.Shell

Create a shell hosting panels, without starting the event loop.

Parameters:
  • panels (Sequence[plesty.lib.ui.panel.Panel]) – Panels to add, in the order they should be docked.

  • title (str) – Window title and layout key.

  • theme (Optional[plesty.lib.ui.theme.Theme]) – Palette; None loads the packaged PLESTY theme.

  • interval_ms (int) – Panel refresh interval in milliseconds.

  • layout_file (Optional[str | pathlib.Path]) – Where the arrangement is stored.

  • remember_layout (bool) – Restore and save the arrangement.

Returns:

The shell, ready for start().

Return type:

plesty.lib.ui.qt.shell.Shell

plesty.lib.ui.qt.run(panels: Sequence[plesty.lib.ui.panel.Panel], *, title: str = 'PLESTY', theme: plesty.lib.ui.theme.Theme | None = None, interval_ms: int = 500, layout_file: str | pathlib.Path | None = None, remember_layout: bool = True) int

Open a shell on panels and run it until the window is closed.

Parameters:
  • panels (Sequence[plesty.lib.ui.panel.Panel]) – Panels to add, in the order they should be docked.

  • title (str) – Window title and layout key.

  • theme (Optional[plesty.lib.ui.theme.Theme]) – Palette; None loads the packaged PLESTY theme.

  • interval_ms (int) – Panel refresh interval in milliseconds.

  • layout_file (Optional[str | pathlib.Path]) – Where the arrangement is stored.

  • remember_layout (bool) – Restore and save the arrangement.

Returns:

The Qt exit code.

Return type:

int

class plesty.lib.ui.qt.PlotRenderer(theme: plesty.lib.ui.theme.Theme | None = None, parent: PySide6.QtWidgets.QWidget | None = None)

Bases: plesty.lib.monitor.base_monitor.Renderer

Draw target that renders a monitor’s traces into a pyqtgraph widget.

The renderer owns its widget (widget), so a panel builds one and hands the widget to the shell:

renderer = PlotRenderer(theme)
monitor.attach(renderer)      # builds the plot items, replays the last data
shell_panel_widget = renderer.widget

Create the plot widget.

Parameters:
  • theme (Optional[plesty.lib.ui.theme.Theme]) – Palette to resolve trace colours and chrome against; None loads the packaged PLESTY theme.

  • parent (Optional[PySide6.QtWidgets.QWidget]) – Parent widget.

theme
widget
plot
caption
_items: dict[str, Any]
_images: dict[str, pyqtgraph.ImageItem]
_texts: dict[str, str]
_lut
setup(monitor: plesty.lib.monitor.base_monitor.Monitor) None

Build one plot item per declared trace.

Parameters:

monitor (plesty.lib.monitor.base_monitor.Monitor) – The monitor that was attached.

Return type:

None

draw(monitor: plesty.lib.monitor.base_monitor.Monitor, data: dict[str, plesty.lib.monitor.base_monitor.TraceData]) None

Feed new payloads into the plot items.

Parameters:
Return type:

None

clear(monitor: plesty.lib.monitor.base_monitor.Monitor) None

Empty every plot item without rebuilding them.

Parameters:

monitor (plesty.lib.monitor.base_monitor.Monitor) – The monitor whose view is being reset.

Return type:

None

_style_plot() None

Apply the theme to the plot background, axes, and grid.

Return type:

None

_build_item(spec: plesty.lib.monitor.base_monitor.TraceSpec, index: int) None

Create the plot item declared by one trace spec.

Parameters:
Return type:

None

_draw_curve(key: str, payload: plesty.lib.monitor.base_monitor.TraceData) None

Update one line or scatter trace.

Parameters:
Return type:

None

_draw_image(key: str, payload: plesty.lib.monitor.base_monitor.TraceData) None

Update one image trace, scaling it onto its physical axes.

Parameters:
Return type:

None

_draw_text(key: str, spec: plesty.lib.monitor.base_monitor.TraceSpec, payload: plesty.lib.monitor.base_monitor.TraceData) None

Record a scalar or text trace for the caption line under the plot.

Parameters:
Return type:

None

class plesty.lib.ui.qt.Recorder(widget: PySide6.QtWidgets.QWidget, path: str | pathlib.Path, *, fps: int = DEFAULT_FPS, every: int = 1)

Capture a window while active; encode a video file on stop.

shell.start_recording("run.mp4", fps=10)
...                                  # the shell captures on every tick
path = shell.stop_recording()        # → run.mp4, or run.gif without ffmpeg

Prepare a recording of widget.

Parameters:
  • widget (PySide6.QtWidgets.QWidget) – The window to capture — normally the shell itself.

  • path (str | pathlib.Path) – Destination file; the suffix is corrected to what the available encoder can actually write.

  • fps (int) – Frame rate of the encoded video.

  • every (int) – Capture one frame per every refreshes, for a run whose rows arrive faster than a video needs them.

Raises:

ValueError – If fps or every is not positive.

widget
path
fps = 10
every = 1
active = False
frames: list[numpy.ndarray] = []
_shape: tuple[int, int] | None = None
_seen = 0
start() None

Begin capturing; a restart discards what was captured before.

Return type:

None

capture() None

Capture one frame, honouring every; ignored when inactive.

Return type:

None

stop() pathlib.Path | None

Stop capturing and encode what was captured.

Returns:

The written file, or None when nothing was captured — a recording that never saw a frame writes no file rather than an empty one.

Return type:

Optional[pathlib.Path]

_encode() pathlib.Path

Write the captured frames, preferring MP4 and falling back to GIF.

Return type:

pathlib.Path

_encode_gif() pathlib.Path

Write an animated GIF, the fallback that needs only Pillow.

Return type:

pathlib.Path

describe() dict[str, Any]

Return what has been captured so far.

Returns:

Whether the recorder is active, the frame count, and the target.

Return type:

dict[str, Any]

class plesty.lib.ui.qt.DockPanel(panel: plesty.lib.ui.panel.Panel, shell: Shell)

Bases: PySide6.QtWidgets.QDockWidget

One sub-window of the shell, wrapping a Panel.

Movable, floatable, and (unless the panel says otherwise) closable — the three features that let an operator rearrange the window at will.

Wrap panel into a sub-window of shell.

Parameters:
panel
title_bar
toggle_floating() None

Pull the panel out of the home window, or drop it back in.

Return type:

None

refresh() None

Tick the panel and mirror its status into the title bar.

Return type:

None

class plesty.lib.ui.qt.PanelTitleBar(dock: DockPanel, title: str, closable: bool = True)

Bases: PySide6.QtWidgets.QWidget

Title bar of a sub-window: state dot, title, live status, controls.

Replaces Qt’s default dock title bar so a panel can show what it is doing without stealing plot area — the same window-chrome language as the plesty.net hero card. Mouse presses are left unhandled on purpose, so the dock widget beneath still starts a drag: this bar is the drag handle that pulls a panel out of the home window.

Build the bar for dock.

Parameters:
  • dock (DockPanel) – The sub-window this bar belongs to.

  • title (str) – Text shown on the left.

  • closable (bool) – Show the close button.

_dock
dot
label
status
_button(glyph: str, tip: str, handler: Any) PySide6.QtWidgets.QToolButton

Create one title-bar button.

Parameters:
  • glyph (str)

  • tip (str)

  • handler (Any)

Return type:

PySide6.QtWidgets.QToolButton

set_status(text: str, tone: str = 'idle') None

Update the status line and the state dot.

Parameters:
  • text (str) – Status text shown at the right of the bar.

  • tone (str) – State name understood by the theme ("running", "idle", "error", "warning").

Return type:

None

set_floating(floating: bool) None

Mark the bar as belonging to a floating window.

Parameters:

floating (bool) – Whether the sub-window is currently floating.

Return type:

None

mouseDoubleClickEvent(event: Any) None

Toggle floating on a double click, as a title bar conventionally does.

Parameters:

event (Any) – The Qt mouse event.

Return type:

None

class plesty.lib.ui.qt.Shell(title: str = 'PLESTY', *, theme: plesty.lib.ui.theme.Theme | None = None, interval_ms: int = 500, layout_file: str | pathlib.Path | None = None, remember_layout: bool = True, parent: PySide6.QtWidgets.QWidget | None = None)

Bases: PySide6.QtWidgets.QMainWindow

Home window hosting every panel as a rearrangeable sub-window.

Create the home window.

Parameters:
  • title (str) – Window title, also the key of the saved layout.

  • theme (Optional[plesty.lib.ui.theme.Theme]) – Palette and stylesheet; None loads the packaged PLESTY theme.

  • interval_ms (int) – Refresh interval — how often every panel is ticked. 500 ms keeps a live spectrum fluent without loading the machine that is also running the measurement.

  • layout_file (Optional[str | pathlib.Path]) – Where to store the arrangement; None uses ~/.plesty/ui/<title>.json.

  • remember_layout (bool) – Restore the stored arrangement on open and save it on close.

  • parent (Optional[PySide6.QtWidgets.QWidget]) – Parent widget.

theme
panels: dict[str, DockPanel]
recorder: plesty.lib.ui.qt.recorder.Recorder | None = None
last_recording: pathlib.Path | None = None
remember_layout = True
layout_file
_timer
add(panel: plesty.lib.ui.panel.Panel, *, area: str | None = None, tabify_with: str | None = None, split_with: str | None = None) DockPanel

Add a panel as a sub-window.

A panel joining an area that already holds one is split against the last panel there rather than dropped into the area as a whole. Only a split puts a real handle between two panels: docks that merely share an area — or worse, sit in different areas — cannot be resized against each other, which is how a panel ends up as an unadjustable strip.

Parameters:
  • panel (plesty.lib.ui.panel.Panel) – The panel to host.

  • area (Optional[str]) – Docking area override; defaults to panel.area.

  • tabify_with (Optional[str]) – Name of an already-added panel to tab this one with, instead of giving it its own space.

  • split_with (Optional[str]) – Name of the panel to split against; defaults to the last one added to the same area.

Returns:

The created sub-window.

Raises:

ValueError – If the panel name is already taken, or the panel named in tabify_with / split_with is unknown.

Return type:

DockPanel

_named(name: str, action: str) DockPanel

Return an already-added sub-window by panel name.

Raises:

ValueError – If no panel of that name was added.

Parameters:
  • name (str)

  • action (str)

Return type:

DockPanel

_last_in_area(area: str) DockPanel | None

Return the last docked sub-window of area, if it has one.

Parameters:

area (str)

Return type:

Optional[DockPanel]

apply_weights() None

Size the panels of each area in proportion to their panel weights.

Qt distributes leftover space evenly, which leaves a status strip as tall as a spectrum. Weights say what each panel is worth: a plot 3, a readout 1.

Return type:

None

remove(name: str) None

Remove a panel and release what it holds.

Parameters:

name (str) – Name of the panel to remove; unknown names are ignored.

Return type:

None

start() None

Show the window, restore the layout, and start refreshing panels.

A stored arrangement wins over the panels’ weights — it is what the operator built last time.

Return type:

None

stop() None

Stop refreshing panels.

Return type:

None

set_status(text: str, state: str = 'idle') None

Set the shell-wide status line.

Parameters:
  • text (str) – Message shown in the status bar.

  • state (str) – State name coloring the indicator ("running", "idle", "error", "warning").

Return type:

None

_tick() None

Refresh every panel; a failing panel never stops the others.

Return type:

None

start_recording(path: str | pathlib.Path, *, fps: int = 10, every: int = 1) plesty.lib.ui.qt.recorder.Recorder

Capture the window after every refresh until stop_recording().

Recording belongs to the window rather than to a view, so whatever is docked is captured — including a panel added or floated halfway through a run.

Parameters:
  • path (str | pathlib.Path) – Destination file; the suffix is corrected to what the available encoder can write (MP4, else animated GIF).

  • fps (int) – Frame rate of the encoded video.

  • every (int) – Capture one frame per every refreshes.

Returns:

The recorder, already active.

Return type:

plesty.lib.ui.qt.recorder.Recorder

stop_recording() pathlib.Path | None

Encode what was captured and stop recording.

Returns:

The written file, or None if nothing was being recorded.

Return type:

Optional[pathlib.Path]

_toggle_recording() None

Menu handler: start a recording beside the layout, or stop one.

Return type:

None

save_layout(path: str | pathlib.Path | None = None) pathlib.Path

Store the current arrangement.

Parameters:

path (Optional[str | pathlib.Path]) – Destination file; None uses layout_file.

Returns:

The path written.

Return type:

pathlib.Path

restore_layout(path: str | pathlib.Path | None = None) bool

Restore a stored arrangement, if one exists and still fits.

Panels that were not added this time are simply absent from the restored layout; new ones keep the placement they were added with.

Parameters:

path (Optional[str | pathlib.Path]) – Layout file; None uses layout_file.

Returns:

Whether a layout was restored.

Return type:

bool

reset_layout() None

Return every panel to the area it was added with, undocked from tabs.

Return type:

None

_build_chrome() None

Create the menus and the status bar.

Return type:

None

closeEvent(event: Any) None

Save the arrangement and release the panels before closing.

Parameters:

event (Any) – The Qt close event.

Return type:

None