plesty.lib.ui.qt ================ .. py:module:: plesty.lib.ui.qt .. autoapi-nested-parse:: PySide6 backend of the PLESTY shell. Importing this package requires the ``gui`` extra (``uv add "plesty-lib[gui]"``): PySide6 and pyqtgraph. Everything above it — :class:`~plesty.lib.ui.panel.Panel`, :class:`~plesty.lib.ui.theme.Theme`, :class:`~plesty.lib.ui.monitor_panel.MonitorPanel` — imports without a GUI installed, so headless hosts and CI keep working. Submodules ---------- .. toctree:: :maxdepth: 1 /reference/plesty/lib/ui/qt/app/index /reference/plesty/lib/ui/qt/plot/index /reference/plesty/lib/ui/qt/recorder/index /reference/plesty/lib/ui/qt/shell/index Classes ------- .. autoapisummary:: plesty.lib.ui.qt.PlotRenderer plesty.lib.ui.qt.Recorder plesty.lib.ui.qt.DockPanel plesty.lib.ui.qt.PanelTitleBar plesty.lib.ui.qt.Shell Functions --------- .. autoapisummary:: plesty.lib.ui.qt.application plesty.lib.ui.qt.build_shell plesty.lib.ui.qt.run Package Contents ---------------- .. py:function:: application(name: str = 'PLESTY') -> PySide6.QtWidgets.QApplication Return the running Qt application, creating it when there is none. :param name: Application name, used by the window manager. :returns: The :class:`QApplication` instance. .. py:function:: build_shell(panels: Sequence[plesty.lib.ui.panel.Panel], *, title: str = 'PLESTY', theme: Optional[plesty.lib.ui.theme.Theme] = None, interval_ms: int = 500, layout_file: Optional[str | pathlib.Path] = None, remember_layout: bool = True) -> plesty.lib.ui.qt.shell.Shell Create a shell hosting *panels*, without starting the event loop. :param panels: Panels to add, in the order they should be docked. :param title: Window title and layout key. :param theme: Palette; ``None`` loads the packaged PLESTY theme. :param interval_ms: Panel refresh interval in milliseconds. :param layout_file: Where the arrangement is stored. :param remember_layout: Restore and save the arrangement. :returns: The shell, ready for :meth:`~plesty.lib.ui.qt.shell.Shell.start`. .. py:function:: run(panels: Sequence[plesty.lib.ui.panel.Panel], *, title: str = 'PLESTY', theme: Optional[plesty.lib.ui.theme.Theme] = None, interval_ms: int = 500, layout_file: Optional[str | pathlib.Path] = None, remember_layout: bool = True) -> int Open a shell on *panels* and run it until the window is closed. :param panels: Panels to add, in the order they should be docked. :param title: Window title and layout key. :param theme: Palette; ``None`` loads the packaged PLESTY theme. :param interval_ms: Panel refresh interval in milliseconds. :param layout_file: Where the arrangement is stored. :param remember_layout: Restore and save the arrangement. :returns: The Qt exit code. .. py:class:: PlotRenderer(theme: Optional[plesty.lib.ui.theme.Theme] = None, parent: Optional[PySide6.QtWidgets.QWidget] = None) Bases: :py:obj:`plesty.lib.monitor.base_monitor.Renderer` Draw target that renders a monitor's traces into a pyqtgraph widget. The renderer owns its widget (:attr:`widget`), so a panel builds one and hands the widget to the shell: .. code-block:: python renderer = PlotRenderer(theme) monitor.attach(renderer) # builds the plot items, replays the last data shell_panel_widget = renderer.widget Create the plot widget. :param theme: Palette to resolve trace colours and chrome against; ``None`` loads the packaged PLESTY theme. :param parent: Parent widget. .. py:attribute:: theme .. py:attribute:: widget .. py:attribute:: plot .. py:attribute:: caption .. py:attribute:: _items :type: dict[str, Any] .. py:attribute:: _images :type: dict[str, pyqtgraph.ImageItem] .. py:attribute:: _texts :type: dict[str, str] .. py:attribute:: _lut .. py:method:: setup(monitor: plesty.lib.monitor.base_monitor.Monitor) -> None Build one plot item per declared trace. :param monitor: The monitor that was attached. .. py:method:: 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. :param monitor: The monitor that produced the update. :param data: Trace key → payload. .. py:method:: clear(monitor: plesty.lib.monitor.base_monitor.Monitor) -> None Empty every plot item without rebuilding them. :param monitor: The monitor whose view is being reset. .. py:method:: _style_plot() -> None Apply the theme to the plot background, axes, and grid. .. py:method:: _build_item(spec: plesty.lib.monitor.base_monitor.TraceSpec, index: int) -> None Create the plot item declared by one trace spec. .. py:method:: _draw_curve(key: str, payload: plesty.lib.monitor.base_monitor.TraceData) -> None Update one line or scatter trace. .. py:method:: _draw_image(key: str, payload: plesty.lib.monitor.base_monitor.TraceData) -> None Update one image trace, scaling it onto its physical axes. .. py:method:: _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. .. py:class:: 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. .. code-block:: python 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*. :param widget: The window to capture — normally the shell itself. :param path: Destination file; the suffix is corrected to what the available encoder can actually write. :param fps: Frame rate of the encoded video. :param every: 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. .. py:attribute:: widget .. py:attribute:: path .. py:attribute:: fps :value: 10 .. py:attribute:: every :value: 1 .. py:attribute:: active :value: False .. py:attribute:: frames :type: list[numpy.ndarray] :value: [] .. py:attribute:: _shape :type: Optional[tuple[int, int]] :value: None .. py:attribute:: _seen :value: 0 .. py:method:: start() -> None Begin capturing; a restart discards what was captured before. .. py:method:: capture() -> None Capture one frame, honouring :attr:`every`; ignored when inactive. .. py:method:: stop() -> Optional[pathlib.Path] 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. .. py:method:: _encode() -> pathlib.Path Write the captured frames, preferring MP4 and falling back to GIF. .. py:method:: _encode_gif() -> pathlib.Path Write an animated GIF, the fallback that needs only Pillow. .. py:method:: describe() -> dict[str, Any] Return what has been captured so far. :returns: Whether the recorder is active, the frame count, and the target. .. py:class:: DockPanel(panel: plesty.lib.ui.panel.Panel, shell: Shell) Bases: :py:obj:`PySide6.QtWidgets.QDockWidget` One sub-window of the shell, wrapping a :class:`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*. :param panel: The panel to host. :param shell: The home window. .. py:attribute:: panel .. py:attribute:: title_bar .. py:method:: toggle_floating() -> None Pull the panel out of the home window, or drop it back in. .. py:method:: refresh() -> None Tick the panel and mirror its status into the title bar. .. py:class:: PanelTitleBar(dock: DockPanel, title: str, closable: bool = True) Bases: :py:obj:`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*. :param dock: The sub-window this bar belongs to. :param title: Text shown on the left. :param closable: Show the close button. .. py:attribute:: _dock .. py:attribute:: dot .. py:attribute:: label .. py:attribute:: status .. py:method:: _button(glyph: str, tip: str, handler: Any) -> PySide6.QtWidgets.QToolButton Create one title-bar button. .. py:method:: set_status(text: str, tone: str = 'idle') -> None Update the status line and the state dot. :param text: Status text shown at the right of the bar. :param tone: State name understood by the theme (``"running"``, ``"idle"``, ``"error"``, ``"warning"``). .. py:method:: set_floating(floating: bool) -> None Mark the bar as belonging to a floating window. :param floating: Whether the sub-window is currently floating. .. py:method:: mouseDoubleClickEvent(event: Any) -> None Toggle floating on a double click, as a title bar conventionally does. :param event: The Qt mouse event. .. py:class:: Shell(title: str = 'PLESTY', *, theme: Optional[plesty.lib.ui.theme.Theme] = None, interval_ms: int = 500, layout_file: Optional[str | pathlib.Path] = None, remember_layout: bool = True, parent: Optional[PySide6.QtWidgets.QWidget] = None) Bases: :py:obj:`PySide6.QtWidgets.QMainWindow` Home window hosting every panel as a rearrangeable sub-window. Create the home window. :param title: Window title, also the key of the saved layout. :param theme: Palette and stylesheet; ``None`` loads the packaged PLESTY theme. :param interval_ms: 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. :param layout_file: Where to store the arrangement; ``None`` uses ``~/.plesty/ui/