spacr.qt.widgets.ambient

Ambient animated backdrop — soft motion behind every module screen.

The sequencing screen has its own backdrop (spacr.qt.widgets.dna_rain, the ATGC cascade). This is the one for everything else: a slow, diffuse animation that sits behind the settings form and the console, takes no focus and no mouse events, and can be switched off entirely in Preferences.

Seven themes, chosen so each reads as a different kind of movement rather than as a re-skin of the same one:

blobs (default)

Big and small colour blobs drifting over the page, each pulsing in size on its own period. They overlap and blend, so the result reads as soft colour fields rather than as a bag of circles.

aurora

Three overlapping curtains of vertical rays, folding along their own length. The folds are travelling waves — several superposed frequencies running lengthwise along the arc — with brightness surges on a separate schedule, a sharp lower edge, a diffuse top, and the real thing’s vertical colour order: green through the body, red high up, a violet fringe underneath.

ripple

Concentric rings expanding out of three fixed sources and fading as they grow, like rain on water. Soft-edged, so it never reads as line work.

drift

A slow starfield in three parallax layers: small, dim, slow ones behind; bigger, brighter, faster ones in front. The one crisp theme. It travels up, down, or every which way — see DRIFT_DIRECTIONS.

bokeh

Out-of-focus points of light, the way a fluorescence field looks off the focal plane: an aperture image is a disc with a bright rim, not a Gaussian smudge, and the ones further out of focus are larger and flatter.

cells

Cells drifting through the field, turning as they go — a soft body, a slightly brighter membrane where the edge is seen nearly edge-on, and a distinctly brighter nucleus set off centre.

resonance

Sand on a vibrating plate, gathering along the lines that do not move. The only theme with an input other than the clock: while the music bed is playing it is driven by the bed’s own precomputed envelope and spectrum (spacr.qt.resonance), and in silence — which is almost everybody, because sound is off on a fresh install — it breathes on its own. See ResonanceEngine for why that input is read in ResonanceEngine.advance() and nowhere else.

There is an eighth engine, fractal, and it is not one of the seven: it is not in AMBIENT_THEMES, no menu lists it, no preference can hold it, and the only way to see it is to start the application with the spaceout command instead of spacr. See SPACEOUT_THEME and FractalEngine.

The random direction of drift produces Brownian-style motion without duplicating the starfield as a separate theme. Themes dominated by many antialiased lines or per-pixel noise are omitted because their raster cost is too high for an always-running backdrop.

Palettes

Every theme declares the palettes it offers (palettes_for()), because a palette that works as a 400 px blob does not necessarily work as a 2 px star. PALETTE_SETS holds the colours themselves; spacr uses the three brand hues from the module-maturity legend, and okabe is the Okabe–Ito set for red–green colour deficiency (see its note).

Both dark and light

spaCR ships seven themes, and a blob set tuned only for a near-black page turns to mud on a white one. So the composition mode follows the background:

  • dark page -> CompositionMode_Plus. Overlapping blobs add up and glow, which is what makes two circles read as one colour field. Additive over a light page just clips to white and the whole effect vanishes.

  • light page -> CompositionMode_Multiply, with the palette colour mixed toward white first. Multiply is the exact dual: overlaps get darker and still blend hue-wise, so the same geometry reads the same way. SourceOver would let later blobs cover earlier ones, producing discrete discs instead of a blended field.

AmbientWidget.set_background_color() re-derives all of that, so a live theme switch is one call.

Cost

This paints behind every module screen, on machines that are simultaneously running Cellpose on a GPU and a 40-plate pipeline, so cost is a correctness requirement rather than a nicety. Two things get it there:

  1. The timer stops whenever the widget is not on screen — hidden, on another tab, or in a minimised window. Zero frames, zero CPU. These screens stay open for hours, so this is the whole ball game.

  2. The soft themes are painted into a small reusable QImage and scaled up, never at full resolution. The buffer’s long edge is whatever the theme declares (_BufferedEngine.base_edge) times the user’s resolution setting, so the diffuse fields shade ~37 000 pixels instead of ~2 000 000 and the aurora, which has real structure in it, shades ~520 000. On the synchronous path the one allocation happens on resize and never per frame; the shading thread copies the finished buffer once a frame so the GUI thread can blit it while the next one is being drawn, which measures 0.003 ms for blobs and 0.035 for the aurora’s 2 MiB — 2 % of the shading pass it makes safe.

  3. The shading happens on its own thread (_FrameProducer), so the GUI thread’s whole share of a frame is one drawImage. That is the next section, and it is the one that matters while a pipeline is running.

While a run is going

The animation can lag while a job is running, and the cause is not the obvious one. Measurements on a real X server at 1920x1080 with a real ConsolePanel under the real stylesheet and a real Qt event loop, blobs at the shipped 24 fps cap, best of five interleaved rounds:

condition

delivered

GUI paint

idle

25.0 fps

2.04 ms

a numpy thread (1024² matmul + FFT), flat out

24.5 fps

2.10 ms

200 console lines a second, nothing else

24.9 fps

1.27 ms

one pure-Python thread

24.5 fps

17.21 ms

a worker doing Python work and printing

17.3 fps

17.16 ms

So CPU saturation is not a cause: numpy releases the interpreter lock and a core burning flat out costs this module nothing. A signal flood is not a cause on its own: 200 lines a second are free, and it only bites in the thousands, where the console’s own per-line work saturates the GUI thread and nothing in this module can help. What is left is the interpreter lock: identical drawing work, eleven times slower, because the shading pass is Python and numpy and something else is holding the lock.

Translucent overlays can also trigger repaints outside the animation timer. The console sits over this widget, so each new line can expose it and request a full frame even while the animation timer is stopped. Expensive shading must therefore remain off the GUI thread even when the frame rate is capped.

The frame is split at the seam where the cost occurs. Per theme, milliseconds, idle against one Python thread, min of nine interleaved rounds:

theme

shading (moved)

soften + blit (stays)

blobs

0.240 -> 0.572

0.651 -> 1.097

aurora

1.396 -> 7.176

0.797 -> 1.043

ripple

0.367 -> 0.589

0.644 -> 1.196

bokeh

0.663 -> 3.533

0.679 -> 1.008

cells

0.538 -> 26.179

0.663 -> 0.909

drift

0.528 -> 1.084

(no buffer)

resonance

1.072 -> see below

0.842 -> 1.115

resonance is the one row whose contended figure is a range rather than a number, and the shape of its shading is the reason. The others are one long pass of QPainter calls; the plate is dozens of small NumPy calls, and each one gives the lock back and then queues for it again, so what the cell would measure is how often the shading thread was descheduled rather than how much work the theme does. Four nine-round repeats of the protocol above gave medians of 10, 174, 286 and 407 ms on the same machine. Two things make that liveable and both are already here: the cost is paid on the producer thread, so a late frame is a repeated frame (AmbientWidget.repeated_frames) and never a slow interface; and while a run is going the process holds spacr.qt.gil_priority.BUSY_INTERVAL, where the same measurement is 6 to 24 ms. Its row was taken later than the rest and on a busier machine — blobs read 0.262 and 0.749 -> 1.002 in the same run — so read it against those rather than against the table.

The shading pass is sensitive to interpreter-lock contention, whereas the Qt blit remains inexpensive. _BufferedEngine.shade() therefore runs in _FrameProducer, while _BufferedEngine.blit() stays on the GUI thread. If a shaded frame is not ready, the widget repeats the previous frame instead of blocking the interface.

blobs, the default, goes 17.3 fps to 24.7 on the same worker. What this does not address is a genuinely chatty run: at 200 lines a second both land at about 4 fps, because by then the GUI thread is inside ConsolePanel and not in here at all. Two levers finish the job and neither is in this file — sys.setswitchinterval(0.001) in the Qt bootstrap (measured independently: 32 % of the frame rate to 99 %, for about 6 % of the worker’s throughput) and coalescing PipelineWorker.line_ready.

Three constraints shape the implementation. Animation periods are sampled from continuous ranges rather than replayed from a precomputed loop, avoiding visible jumps and large frame caches. Shading runs outside the GUI thread; if a frame is late, the previous frame is repeated and counted by AmbientWidget.repeated_frames. The animation clock remains on the GUI thread, and rendered frames remain deterministic functions of (seed, clock, size).

drift keeps the synchronous path, and its row above is the reason: it is the one engine with no buffer, it degrades the least of the seven (2.1x against 48.7x for cells), and threading it would mean publishing a full-resolution frame — 7.91 MiB a slot against 126.6 KiB for blobs — to buy the smallest improvement on the list.

Performance depends on hardware, display size, theme, and concurrent work. The settings have predictable relative costs: detail is approximately quadratic, density is approximately linear, and blur is inexpensive for the six buffered themes. drift has no buffer, so its blur is a second wider pass per dot and is capped by DRIFT_HALO_MAX_PX. The WORK_BUDGET limits combinations of density and detail that would make the backdrop compete with analysis work. Hidden widgets stop rendering entirely.

Classes

AmbientWidget

The live backdrop: paints an AmbientEngine at a capped rate.

Motion

Every user control that shapes the animation, in one value.

Functions

animation_label(→ str)

Human label for an entry of ANIMATION_CHOICES.

animation_note(→ str)

One-line description of an animation choice, for a tooltip.

default_palette_for(→ str)

The palette theme falls back to — DEFAULT_PALETTE when it

dressed(→ Tuple[str, str])

Resolve the ambient theme and palette for the current launch mode.

drift_direction_label(→ str)

Human label for a starfield direction, for a menu.

drift_direction_note(→ str)

One-line description of a starfield direction, for a tooltip.

install_ambient(→ AmbientWidget)

Put a live ambient backdrop behind host.

is_animation_choice(→ bool)

True for anything the Animation preference may hold — including

is_valid_drift_direction(→ bool)

True when name is one of DRIFT_DIRECTIONS. Never raises.

is_valid_palette(→ bool)

True when palette is offered by theme. Never raises.

is_valid_theme(→ bool)

True when name is one of AMBIENT_THEMES.

palette_colors(→ Tuple[str, ...])

The #rrggbb colours behind palette, for theme.

palette_label(→ str)

Human label for palette as offered by theme.

palette_note(→ str)

One-line description of palette, for a tooltip.

palettes_for(→ Tuple[str, ...])

The palette names theme offers, in menu order.

preferred_motion(→ Motion)

The animation controls, from the user's preferences.

theme_label(→ str)

Human label for name, for a menu. Raises on an unknown theme.

theme_note(→ str)

One-line description of name, for a tooltip.

total_frames_painted(→ int)

How many ambient frames this process has painted. For tests.

Module Contents

class spacr.qt.widgets.ambient.AmbientWidget(parent: PySide6.QtWidgets.QWidget | None = None, *, theme: str = DEFAULT_THEME, palette: str = DEFAULT_PALETTE, background: PySide6.QtGui.QColor | str | None = None, backdrop=None, fps: int = DEFAULT_FPS, seed: int | None = None, blur: float | None = None, speed: float | None = None, size: float | None = None, resolution: float | None = None, density: float | None = None, direction: str | None = None, corner_radius: int = 0)[source]

Bases: PySide6.QtWidgets.QWidget

The live backdrop: paints an AmbientEngine at a capped rate.

Screen content sits in front of it, so it never takes focus, is transparent to mouse events, and lowers itself to the bottom of the sibling stacking order. It is fully opaque — it paints the page colour (or the theme wallpaper) itself and the animation on top — so the widget it covers has nothing to repaint underneath.

Parameters:
  • parent – parent widget; follow_parent() sizes it to that.

  • theme – one of AMBIENT_THEMES.

  • palette – one of palettes_for() for that theme. A palette that exists but is not offered by the theme is downgraded to the theme’s default (stale preferences must not break a screen); an unknown name raises.

  • background – the flat colour under the animation; defaults to the current theme’s page colour.

  • backdrop – an image to paint under the animation instead of the flat colour — a path, a QPixmap/QImage, or None. Give it the Space/Cell wallpaper and the animation composites over the picture rather than replacing it.

  • fps – frame-rate cap.

  • seed – RNG seed, for a reproducible animation.

  • blur – how much the finished picture is softened; None reads Preferences.

  • speed – motion multiplier; None reads Preferences.

  • size – element-size multiplier; None reads Preferences.

  • resolution – how much detail is shaded, as a multiplier on the theme’s own buffer; None reads Preferences.

  • density – how many elements are drawn, as a multiplier on the theme’s own count; None reads Preferences.

  • direction – which way the starfield travels, one of DRIFT_DIRECTIONS; None reads Preferences. Meaningless to the other themes, and kept anyway so switching away and back does not lose it.

  • corner_radius – round the backdrop’s own corners by this many px; 0 leaves it square. CLIPPED, not masked – a mask region gives stair-stepped corners against the card’s anti-aliased rim – and applied before the base fill so the flat page colour is rounded with the animation rather than showing at the corners behind it.

Build the widget and start its engine.

Parameters:

parent – parent widget.

advance_frame(dt: float) → None[source]

Step the animation by dt seconds and schedule a repaint.

Called directly by the tests and by the tutorial recorder, so no caller ever waits on a real clock. Re-shades before it returns, so the next paint shows the frame that was asked for rather than whatever the shading thread last finished — the timer does not come through here for exactly that reason (_on_tick()).

Parameters:

dt – seconds to step; the engine scales the step by its speed, and a step that is not positive leaves the clock where it is.

backdrop() → PySide6.QtGui.QPixmap | None[source]

The image painted under the animation, or None.

background_color() → PySide6.QtGui.QColor[source]

The colour painted behind the animation.

A copy, so a caller cannot recolour this widget in place.

Returns:

the background colour.

blur() → float[source]

How much the picture is softened; 0.0 is the shipped animation.

changeEvent(event) → None[source]

Follow a live theme switch when nobody else is going to.

A host that passed its own background owns that colour and is expected to re-set it (that is what app_screen does, because it also has to re-resolve the wallpaper). A host that did not gets this for free instead of a stale dark rectangle on a white page.

Parameters:

event – the change event; it goes to the base class first, and only an ApplicationPaletteChange is acted on.

density() → float[source]

How many elements are drawn; 1.0 is each theme’s own count.

direction() → str[source]

Which way the starfield travels. Meaningless to the others, and kept anyway, so switching themes and back does not lose it.

eventFilter(obj, event)[source]

Follow the parent’s size; pause when the window is minimised.

Parameters:
  • obj – the object the event is for — the parent (whose resize this widget follows) or the watched top-level window.

  • event – the event; resize, window-state, hide, show, move and screen-change types are acted on, and every event is still passed on to the base-class filter.

focusInEvent(event) → None[source]

Reject even programmatic focus; this widget is decorative only.

Parameters:

event – the focus event; it is ignored and focus is cleared.

follow_parent() → None[source]

Track the parent’s geometry and sit below its other children.

fps() → int[source]

The cap on repaints per second.

A CAP, NOT A RATE. This is a backdrop and must not take frames from whatever the user is doing in front of it.

Returns:

the frame cap.

frames_shaded() → int[source]

Frames the shading thread has finished for this backdrop.

Below frames_painted under load, by design: the difference is repeated_frames.

hideEvent(event)[source]

The whole performance story: a screen the user is not looking at costs nothing. Qt sends this to the children of a hidden parent too, so switching tabs stops the animation on the tab you left.

Parameters:

event – the hide event; passed on to the base class before the animation stops.

is_animating() → bool[source]

The requested state, whether or not the widget is on screen.

is_running() → bool[source]

True while the animation timer is ticking.

paintEvent(event)[source]

Put the page down, then the newest frame the shading thread has.

This method never waits for anything. That is the requirement the whole change exists to meet — “a frame that is not ready is a repeated frame, never a blocked GUI thread” — and it is met the only way it can be: _FrameProducer.latest() refuses the slot rather than blocking on it, the widget keeps a reference to the frame it is already showing, and a paint with nothing new blits that one again and counts it in repeated_frames.

With a shading thread the engine lock is never waited on here, and with no shading thread it cannot be contended — the one path that takes it is the case where nothing has been published yet (a widget shown at zero size and resized in the same tick), and it takes it without blocking, settling for the flat page if the thread is mid-frame.

Repainting is not only the timer’s doing: every console line the window paints over a translucent surface exposes this widget and Qt asks it for a whole frame. Measured on a real X server with the real stylesheet, one ambient repaint per console line — 0.99 of them, with the animation timer stopped. That is the fps cap being bypassed entirely, and it is why the cost of a repaint matters more than the cap suggests: shading it cost 1.7 ms idle and 22 ms under a Python worker, and blitting an already-shaded frame costs 0.24-0.32 ms at every load measured.

Parameters:

event – the paint event; a new frame is taken from the shading thread only when its rect() covers the whole widget, otherwise the current frame is repainted.

palette_name() → str[source]

The ambient palette’s name.

Not palette() — QWidget already owns that name and it returns a QPalette.

resolution() → float[source]

How much detail is shaded; 1.0 is each theme’s own buffer.

set_animating(on: bool) → None[source]

Pause or resume without destroying anything.

A paused widget keeps its last frame on screen and its engine in memory; its timer stops ticking. This is the “off” switch for the Preferences toggle when the user wants the colours but not the motion — turning the feature off entirely is the install site’s job, not this widget’s.

Parameters:

on – truthy to animate, falsy to pause.

set_backdrop(source) → None[source]

Paint source under the animation instead of the flat colour.

The animation composites (adds on dark, multiplies on light), so a wallpaper handed in here shows through it rather than being replaced. None goes back to the flat fill.

Parameters:

source – an image path, QPixmap or QImage, or None; anything that does not load as a non-null pixmap also gives the flat fill.

set_background_color(color: PySide6.QtGui.QColor | str) → None[source]

Set the flat fill under the animation.

This is also what tells the engine whether it is painting on a dark or a light page, which picks additive versus multiply compositing — so it must be called on a live theme switch, or a dark-tuned frame ends up on a white page.

Parameters:

color – a QColor or any string QColor accepts; an invalid colour keeps the current one, and alpha is forced opaque. The widget then stops following the application theme.

set_blur(value: float) → None[source]

Set the softening. Clamped to BLUR_RANGE.

Parameters:

value – the blur amount, converted with float.

set_density(value: float) → None[source]

Set the element-count multiplier. Clamped to DENSITY_RANGE.

Parameters:

value – the density multiplier, converted with float.

set_direction(name: str) → None[source]

Set the starfield direction. An unknown name is ignored.

Parameters:

name – one of DRIFT_DIRECTIONS.

set_fps(fps: int) → None[source]

Cap the frame rate. Caps the shading thread with it, so a lowered cap actually reduces the work rather than just how much of it is shown.

Parameters:

fps – frames per second, converted with int and clamped to MIN_FPS to MAX_FPS.

set_palette(name: str) → None[source]

Switch colour set, live, keeping the motion exactly where it is.

Raises ValueError if the current theme does not offer name — an explicit request for a palette is not something to silently substitute. The one exception is the spaceout dressing, where the request is replaced rather than refused, for the reason given in dressed().

Parameters:

name – a palette the current theme offers.

set_resolution(value: float) → None[source]

Set the detail multiplier. Clamped to RESOLUTION_RANGE.

Parameters:

value – the resolution multiplier, converted with float.

set_size_scale(value: float) → None[source]

Set the element-size multiplier. Clamped to SIZE_RANGE.

Parameters:

value – the size multiplier, converted with float.

set_speed(value: float) → None[source]

Set the motion multiplier. Clamped to SPEED_RANGE.

Takes effect on the next step, so nothing already on screen moves.

Parameters:

value – the speed multiplier, converted with float.

set_theme(name: str) → None[source]

Switch animation, live. Raises ValueError on an unknown name.

The clock carries over and the old engine is dropped — there is no second timer and no second engine, so a user flipping through the menu cannot leave anything ticking behind them. If the current palette is not one this theme offers, it downgrades to the theme’s default (see palettes_for() for why the lists differ).

Under the spaceout dressing every request lands on the fractal, whoever asked and for whatever — including spacr.qt.preferences.apply_ambient_preferences(), which calls this with the stored animation on every settings save. See dressed().

Parameters:

name – a paintable theme name.

set_time(seconds: float) → None[source]

Jump the animation clock and repaint.

Parameters:

seconds – the new clock value, in animation seconds (the clock advance_frame() steps, already scaled by speed).

shading_thread_alive() → bool[source]

Whether a shading thread is running for this backdrop.

The CPU guarantee used to be a claim about a timer and is now also a claim about a thread, so it needs something to assert on: a backdrop behind a screen nobody is looking at must not be keeping a core warm.

showEvent(event)[source]

Start animating, and follow the window this widget belongs to.

Parameters:

event – the Qt show event.

size_scale() → float[source]

The element-size multiplier; 1.0 is the shipped animation.

Not size() — QWidget already owns that name and it returns a QSize.

speed() → float[source]

The motion multiplier; 1.0 is the shipped animation.

start() → None[source]

Start the animation timer and shading worker if not already running.

The timer and worker share one lifetime. Hiding the widget, switching tabs, minimizing the window, or disabling ambient animation stops both. A widget that is never shown uses the synchronous rendering path.

stop() → None[source]

Stop ticking and retire the shading thread. Costs exactly nothing while stopped — no timer, no thread, and no frame held in memory.

Dropping the published frame matters because these screens stay built: a dozen module screens the user has visited would otherwise each keep a slot warm behind a tab nobody is on, which is 2 MiB apiece for the aurora. The next start() shades a replacement before it starts the thread, so there is nothing to show for it.

theme() → str[source]

Which ambient theme is being painted.

Returns:

the theme’s name.

time() → float[source]

The animation clock, in seconds.

property engine: AmbientEngine[source]

The live engine. Replaced wholesale by set_theme().

class spacr.qt.widgets.ambient.Motion[source]

Bases: NamedTuple

Every user control that shapes the animation, in one value.

A named tuple rather than five arguments because the set grows: it went from three to five in one change, and every install site that had unpacked a plain tuple would have broken.

Parameters:
  • blur – softness of the painted shapes; the widget clamps it to BLUR_RANGE.

  • speed – animation-clock multiplier, clamped to SPEED_RANGE.

  • size – element-size multiplier, clamped to SIZE_RANGE.

  • resolution – detail (render buffer) multiplier, clamped to RESOLUTION_RANGE.

  • density – element-count multiplier, clamped to DENSITY_RANGE.

  • direction – starfield drift direction, one of DRIFT_DIRECTIONS; an unknown name falls back to the default.

spacr.qt.widgets.ambient.animation_label(name: str) → str[source]

Human label for an entry of ANIMATION_CHOICES.

“None” rather than “Off”: the row is called Animation and this is one of the animations it can be set to, the way a font size can be set to zero.

Parameters:

name – NO_ANIMATION or a paintable theme name; any other name raises ValueError.

spacr.qt.widgets.ambient.animation_note(name: str) → str[source]

One-line description of an animation choice, for a tooltip.

The note for “None” states the cost, because that is the only reason a reader picks it — and the claim is asserted rather than advertised: see tests/qt/test_ambient_none.py, which counts painted frames over a real second instead of trusting this sentence.

Parameters:

name – NO_ANIMATION or a paintable theme name; any other name raises ValueError.

spacr.qt.widgets.ambient.default_palette_for(theme: str) → str[source]

The palette theme falls back to — DEFAULT_PALETTE when it is on offer, otherwise the first one listed.

Parameters:

theme – a paintable theme name; an unknown one raises ValueError.

spacr.qt.widgets.ambient.dressed(theme: str, palette: str) → Tuple[str, str][source]

Resolve the ambient theme and palette for the current launch mode.

Standard launches preserve the requested pair. Spaceout launches return SPACEOUT_THEME and SPACEOUT_PALETTE.

Parameters:
  • theme – the requested theme name; returned as given unless the spaceout dressing is on. It is not validated here.

  • palette – the requested palette name, treated the same way.

spacr.qt.widgets.ambient.drift_direction_label(name: str) → str[source]

Human label for a starfield direction, for a menu.

Parameters:

name – one of DRIFT_DIRECTIONS; any other value raises ValueError.

spacr.qt.widgets.ambient.drift_direction_note(name: str) → str[source]

One-line description of a starfield direction, for a tooltip.

Parameters:

name – one of DRIFT_DIRECTIONS; any other value raises ValueError.

spacr.qt.widgets.ambient.install_ambient(host: PySide6.QtWidgets.QWidget, layout=None, *, theme: str = DEFAULT_THEME, palette: str = DEFAULT_PALETTE, backdrop=None, corner_radius: int = 0, **kwargs) → AmbientWidget[source]

Put a live ambient backdrop behind host.

The widget becomes a child of host, tracks its geometry, and is lowered to the bottom of the sibling stacking order so every screen widget paints in front of it. It takes no focus and no mouse events, and it does not tick until host is actually on screen.

Note that a backdrop is only as visible as its siblings are transparent: under dark and light every container is an opaque page colour, and an animation behind them reaches the eye through nothing but the few pixels of layout spacing. The caller is responsible for clearing those surfaces first — see AppScreen._clear_page_surfaces.

Parameters:
  • host – the screen the animation sits behind.

  • layout – accepted for signature compatibility with spacr.qt.widgets.dna_rain.install_dna_rain(), which appends its settings bar to it. The ambient backdrop has no on-screen controls — it is configured in Preferences — so nothing is added here, and the two installers stay interchangeable at a call site.

  • theme – one of AMBIENT_THEMES.

  • palette – one of palettes_for() for that theme.

  • backdrop – wallpaper to composite over; see AmbientWidget.set_backdrop().

  • corner_radius – Radius in pixels used to clip the backdrop. The default of zero leaves square corners. For a frameless dialog containing a rounded card, use the card’s radius so the backdrop does not extend beyond its corners.

  • kwargs – forwarded to AmbientWidget (background, fps, seed, blur, speed, size, resolution, density, direction). Everything from blur on defaults to the user’s preferences, so a caller that does not care about them should not pass them.

Returns:

the widget, already shown and lowered.

spacr.qt.widgets.ambient.is_animation_choice(name) → bool[source]

True for anything the Animation preference may hold — including NO_ANIMATION, which is_valid_theme() rejects because it cannot be painted.

Parameters:

name – the value to test, typically a stored preference.

spacr.qt.widgets.ambient.is_valid_drift_direction(name) → bool[source]

True when name is one of DRIFT_DIRECTIONS. Never raises.

Parameters:

name – the value to test.

spacr.qt.widgets.ambient.is_valid_palette(theme, palette) → bool[source]

True when palette is offered by theme. Never raises.

Parameters:
  • theme – the theme name; an unknown theme offers nothing.

  • palette – the palette name to look for among theme’s palettes.

spacr.qt.widgets.ambient.is_valid_theme(name) → bool[source]

True when name is one of AMBIENT_THEMES.

The predicate exists so a caller validating stored preferences does not have to catch ValueError from the strict accessors below.

Parameters:

name – the value to test, typically a stored preference.

spacr.qt.widgets.ambient.palette_colors(theme: str, palette: str) → Tuple[str, ...][source]

The #rrggbb colours behind palette, for theme.

Parameters:
  • theme – a paintable theme name.

  • palette – a palette theme offers; an unknown theme, or a palette the theme does not offer, raises ValueError.

spacr.qt.widgets.ambient.palette_label(theme: str, palette: str) → str[source]

Human label for palette as offered by theme.

Parameters:
  • theme – a paintable theme name.

  • palette – a palette theme offers; an unknown theme, or a palette the theme does not offer, raises ValueError.

spacr.qt.widgets.ambient.palette_note(theme: str, palette: str) → str[source]

One-line description of palette, for a tooltip.

Parameters:
  • theme – a paintable theme name.

  • palette – a palette theme offers; an unknown theme, or a palette the theme does not offer, raises ValueError.

spacr.qt.widgets.ambient.palettes_for(theme: str) → Tuple[str, ...][source]

The palette names theme offers, in menu order.

Never empty. Raises ValueError on an unknown theme rather than returning (), because an empty tuple reads as “this theme has no palettes” and would quietly leave a settings menu blank.

Parameters:

theme – a paintable theme name.

spacr.qt.widgets.ambient.preferred_motion() → Motion[source]

The animation controls, from the user’s preferences.

Read here rather than passed in by every install site, for the same reason _theme_background() is: the two callers that build ambient widgets are a module screen and Home, and neither of them has any business knowing what the animation’s knobs are called. Falls back to the shipped defaults if preferences cannot be read at all.

spacr.qt.widgets.ambient.theme_label(name: str) → str[source]

Human label for name, for a menu. Raises on an unknown theme.

Parameters:

name – a paintable theme name; any other raises ValueError.

spacr.qt.widgets.ambient.theme_note(name: str) → str[source]

One-line description of name, for a tooltip.

Parameters:

name – a paintable theme name; any other raises ValueError.

spacr.qt.widgets.ambient.total_frames_painted() → int[source]

How many ambient frames this process has painted. For tests.