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.
Five data-art materials and three classic themes remain in the menu.
The default is data_art_impulse_lens (spaCR field). The other data-art choices
are spaCR advection, spaCR growth, spaCR waves and spaCR spinn.
The classic choices provide softer motion:
blobsBig 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.
auroraThree 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.
driftA 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.
The older RippleEngine, CellsEngine, BokehEngine
and ResonanceEngine remain importable for direct callers, but are
no longer menu choices or factory entries.
There is also a private fractal engine: 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 several 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.SourceOverwould 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:
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.
Diffuse themes are painted into a small reusable QImage and scaled up. The buffer’s long edge is whatever the theme declares (
_BufferedEngine.base_edge) times the user’s resolution setting, so diffuse fields shade ~37 000 pixels instead of ~2 000 000. The aurora and data-art materials preserve native display detail within the actual screen-pixel budget. Aurora returns an owned frame, avoiding a second full-size copy when a shading worker publishes it.The shading happens on its own thread (
_FrameProducer), so the GUI thread’s whole share of a frame is onedrawImage. 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. The following historical measurements predate native-resolution Aurora and serve as a comparison, not as a current frame-rate claim. 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.
In that historical blobs benchmark, the rate goes from 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 synchronous drawing at full Detail. Lower Detail renders the
same particle population through a bounded image buffer; it does not reduce
Density. Its historical row above predates that buffer path.
Performance depends on hardware, display size, theme, and concurrent work. Density controls population independently of Detail. Detail controls sampling resolution, and buffers stay within the actual screen-pixel budget. Increasing Detail does not trim the selected population. Legacy direct engine callers may still set blur, but Preferences exposes no Blur control. Hidden widgets stop rendering entirely. Historical timings above do not establish native 24 FPS at current maximum controls.
The private growth producer uses _lineage to index reproducible wandering
tips and recursive front forks. Daughter branches remain connected to their
parent filaments; older trails recede as new colonies begin. This is decorative
mycelial artwork, not a model fitted to project measurements.
The field’s background-only left drag uses _set_field_grab and
_step_field_grab for a bounded local spring with continuous release
velocity. AmbientWidget._offer_field_grab publishes the latest handle
without waiting for shading, and AmbientWidget._field_grab_background
excludes scientific canvases and interactive controls. Release returns the
patch without moving or reseeding its underlying material.
Aurora’s buffer_size and buffer_scale retain crisp display sampling;
its private _shade returns a freshly owned frame. Drift’s buffer_size
bounds lower-Detail drawing, while its private _paint_dots paints the same
selected population into that buffer or directly at full Detail.
Classes¶
Base class: a deterministic, time-parameterised painter. |
|
The live backdrop: paints an |
|
Folded curtains of vertical rays, rippling along their own length. |
|
One drifting, pulsing blob, in normalised units. |
|
Diffuse colour blobs, drifting and pulsing. |
|
Defocused points of light: flat discs with bright rims. |
|
One drifting cell, in normalised units. |
|
Cells drifting through the field, turning as they go. |
|
One aurora curtain, in normalised units. |
|
One out-of-focus point source, in normalised units. |
|
A slow starfield in three parallax layers. |
|
Sampling geometry for the primary fractal or a derived bud. |
|
Render a deterministic, animated Julia-set field. |
|
Every user control that shapes the animation, in one value. |
|
One named colour set: what to call it and what it is made of. |
|
One drifting dot, in normalised units. |
|
Chladni figures: sand on a plate that is driven by what is playing. |
|
Concentric rings expanding from a few sources and fading as they grow. |
|
One ripple origin, in normalised units. |
Functions¶
|
Human label for an entry of |
|
One-line description of an animation choice, for a tooltip. |
|
|
|
The palette |
|
Resolve the ambient theme and palette for the current launch mode. |
|
Human label for a starfield direction, for a menu. |
|
One-line description of a starfield direction, for a tooltip. |
|
Publish settled window or panel feedback to its visible field backdrop. |
|
Put a live ambient backdrop behind |
|
True for anything the Animation preference may hold — including |
|
True when |
|
True when |
|
True when |
|
True when |
|
Build the engine for |
|
The |
|
Human label for |
|
One-line description of |
|
The palette names |
|
The animation controls, from the user's preferences. |
Rebuild every running spaceout backdrop whose build settings changed. |
|
|
Return the device-pixel count of the screen containing |
|
Human label for |
|
One-line description of |
|
How many ambient frames this process has painted. For tests. |
Module Contents¶
- class spacr.qt.widgets.ambient.AmbientEngine(colors: Sequence[PySide6.QtGui.QColor | str], background: PySide6.QtGui.QColor | str, seed: int | None = None, blur: float = DEFAULT_BLUR, speed: float = DEFAULT_SPEED, size: float = DEFAULT_SIZE, resolution: float = DEFAULT_RESOLUTION, density: float = DEFAULT_DENSITY, direction: str = DEFAULT_DRIFT_DIRECTION)[source]¶
Base class: a deterministic, time-parameterised painter.
An engine owns no widget and no timer. It holds the constants rolled once from its seed, a clock (
time), and whatever reusable buffer it paints through. Everything it draws is a pure function of(seed, time, width, height, colours, background)— which is what lets a test render the same frame twice and compare it byte for byte, and what letsAmbientWidget.set_theme()swap engines without the animation jumping.Positions are rolled in normalised 0..1 units and multiplied up at paint time, so a resize re-frames the animation instead of re-rolling it.
Every numeric parameter below is CLAMPED to its range rather than rejected: these arrive from saved preferences, and a value that has drifted outside its range should slow the animation down, not refuse to draw it.
- Parameters:
colors – the palette to paint from, as
QColoror as any stringQColoraccepts.background – the colour behind the palette.
seed – the roll that fixes this engine’s constants. The same seed gives the same animation, which is what lets a test compare two renders byte for byte.
Nonerolls a new one.blur – softness of the painted shapes, 0.0 to 3.0.
speed – how fast
timeadvances the animation, 0.1 to 4.0.size – scale of the painted shapes, 0.25 to 2.5.
resolution – scale of the buffer painted through, 0.25 to 2.0. Below 1.0 paints fewer pixels and scales them up, which is the lever that makes the backdrop affordable on a weak GPU.
density – how many shapes are rolled, 0.01 to 3.0.
direction – which way the animation drifts. An unrecognised name falls back to the default rather than raising, for the same reason the numbers are clamped.
Roll the constants this engine paints from.
- Parameters:
colors – the palette to paint with.
background – the colour behind the shapes.
seed – what makes the animation reproducible.
blur – how soft the shapes are drawn.
speed – the animation rate multiplier.
size – the shape scale.
resolution – the render resolution.
density – how many shapes there are.
direction – which way the field drifts.
- advance(dt: float) None[source]¶
Step the clock by
dtseconds. Negative steps are ignored.The step is scaled by
speed, so the clock counts animation seconds rather than wall-clock ones: every period, rate and travel speed in every theme is expressed against this clock and therefore scales with one multiplier.framescounts every call, including the ignored ones, so a test can prove a hidden widget stopped asking for frames rather than only proving that its clock stood still.
- alpha_scale() float[source]¶
What to multiply every element’s peak alpha by, given the density.
Additive compositing means N overlapping shapes are N times the light, so a density control with no compensation is a brightness control wearing a misleading name. Measured, on a page at 0.076: mean frame lightness went from 0.135 at density 1.0 to 0.288 at 3.0. The backdrop would have become the loudest thing behind a settings form — which the alphas in this module were set on a rendered frame specifically to prevent (see
AURORA_ALPHA_DARK).So above 1.0 the field’s light is divided among more elements rather than added to it. Below 1.0 nothing is done: quadrupling the alpha of a quarter as many blobs clips to white rather than compensating, and a sparser field being a quieter one is the right answer anyway.
Density therefore changes the texture of the field — how many shapes it is made of, and how strongly each one states itself — and not how loud the field is. That is the only reading of the control that leaves the backdrop legible at both ends of its range.
- effective_density() float[source]¶
The requested population, independently of render resolution.
Buffered engines enforce the work budget on pixel sampling instead of removing elements when the user increases Detail.
- element_count(base: int, pool: int) int[source]¶
How many of a pool of
poolelements to draw, when the theme’s own count isbase. At least one: a density slider that can empty the screen is an off switch wearing a disguise.
- abstract geometry(width: int, height: int) Tuple[tuple, ...][source]¶
What this engine would draw right now, in pixels.
Every engine answers this and every engine paints from it, so a test can assert on the geometry and know it is asserting on the frame. The tuple shape is per engine and documented there.
- abstract paint(painter: PySide6.QtGui.QPainter, width: int, height: int) None[source]¶
Draw this engine’s current frame.
- Parameters:
painter – the painter to draw with.
width – the widget’s width in pixels.
height – its height in pixels.
- set_background(color: PySide6.QtGui.QColor | str) None[source]¶
Tell the engine what it is painting onto — this is what decides additive versus multiply, so it must be called on a theme switch.
- set_blink_percent(value: float) None[source]¶
Set the percentage of dot centres flashing white, without reseeding.
- set_blur(value: float) None[source]¶
How much the finished picture is softened. 0.0 is untouched.
Clamped to
BLUR_RANGE. Engines that cache anything sized by it drop that cache here, never per frame.
- set_colors(colors: Sequence[PySide6.QtGui.QColor | str]) None[source]¶
Swap the palette without disturbing the motion.
- set_density(value: float) None[source]¶
How many elements the theme draws, as a multiplier on its own count. Clamped to
DENSITY_RANGE.Never re-rolls anything: the pool was built for the top of the range at construction and this only changes how much of it is painted, so the elements that were on screen stay exactly where they were.
- set_direction(name: str) None[source]¶
Which way the elements travel, for the themes that have a way.
Silently ignores an unknown name rather than raising: this reaches every engine, and most of them have nothing to do with it.
- set_max_pixels(pixels: int) None[source]¶
Set the display-pixel ceiling used to size the render buffer.
- set_popup_wave_frequency(value: float) None[source]¶
Set popup-origin waves per minute; zero disables automatic waves.
- set_resolution(value: float) None[source]¶
How many pixels the scene is shaded into, as a multiplier on this theme’s own buffer edge. Clamped to
RESOLUTION_RANGE.
- set_speed(value: float) None[source]¶
Multiply every motion in the theme. Clamped to
SPEED_RANGE.Deliberately a clock multiplier rather than a factor inside
geometry(): a user dragging the slider in Preferences changes how fast the animation goes from here, and never makes what is already on screen jump to a different place.
- set_time(seconds: float) None[source]¶
Jump the clock — used by the tests, and to carry the clock across a theme change.
- property background: PySide6.QtGui.QColor[source]¶
The colour behind the shapes.
A copy, for the same reason as
colors().- Returns:
the background colour.
- 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, gravity_radius: float | None = None, ripples_enabled: bool | None = None, blink_percent: float | None = None, popup_wave_frequency: float | None = None, direction: str | None = None, corner_radius: int = 0)[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe live backdrop: paints an
AmbientEngineat 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, orNone. 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 – retained for older callers; displayed themes ignore blur.
speed – motion multiplier;
Nonereads Preferences.size – element-size multiplier;
Nonereads Preferences.resolution – how much detail is shaded, as a multiplier on the theme’s own buffer;
Nonereads Preferences.density – how many elements are drawn, as a multiplier on the theme’s own count;
Nonereads Preferences.gravity_radius – pointer influence radius, as a fraction of the shorter screen edge; zero disables it.
Nonereads Preferences.ripples_enabled – independent field-wave switch;
Nonereads Preferences.direction – which way the starfield travels, one of
DRIFT_DIRECTIONS;Nonereads 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;
0leaves 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
dtseconds 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.
- 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.
- changeEvent(event) None[source]¶
Follow a live theme switch when nobody else is going to.
A host that passed its own
backgroundowns that colour and is expected to re-set it (that is whatapp_screendoes, 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
ApplicationPaletteChangeis acted on.
- 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.
- 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_paintedunder load, by design: the difference isrepeated_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.
- paintEvent(event) None[source]¶
Paint, timed on the timing timeline when timing is on.
- Parameters:
event – the Qt paint event, passed on to the painter.
- palette_name() str[source]¶
The ambient palette’s name.
Not
palette()—QWidgetalready owns that name and it returns aQPalette.
- 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
sourceunder 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.
Nonegoes back to the flat fill.- Parameters:
source – an image path,
QPixmaporQImage, orNone; 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
QColoror any stringQColoraccepts; an invalid colour keeps the current one, and alpha is forced opaque. The widget then stops following the application theme.
- set_blink_percent(value: float) None[source]¶
Update dot flashes through the normal serialized engine mutation.
- Parameters:
value – percentage of visible dots flashing white, clamped to zero through ten; zero disables blinking.
- set_blur(value: float) None[source]¶
Keep the retired softening control compatible with older callers.
- Parameters:
value – a legacy value; displayed themes remain unsoftened.
- 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_field_effects(effects: dict) None[source]¶
Apply saved Spaceout field switches under the renderer’s lock.
- Parameters:
effects – saved field-effect switches keyed by effect name.
- Returns:
None.
- 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
intand clamped toMIN_FPStoMAX_FPS.
- set_gravity_radius(value: float) None[source]¶
Apply local pointer reach while excluding a concurrent shade pass.
- Parameters:
value – fraction of the shorter screen edge, clamped to [0, 1]; zero disables hover gravity; the explicit field handle remains available.
- set_palette(name: str) None[source]¶
Switch colour set, live, keeping the motion exactly where it is.
Raises
ValueErrorif the current theme does not offername— an explicit request for a palette is not something to silently substitute. The one exception is thespaceoutdressing, where the request is replaced rather than refused, for the reason given indressed().- Parameters:
name – a palette the current theme offers.
- set_popup_wave_frequency(value: float) None[source]¶
Change popup waves without changing pointer reach or dot population.
- Parameters:
value – popup waves per minute, clamped to zero through sixty; zero disables recurring waves. The separate ripple switch controls discrete click, container and window feedback.
- set_resolution(value: float) None[source]¶
Set the detail multiplier. Clamped to
RESOLUTION_RANGE.- Parameters:
value – the resolution multiplier, converted with
float.
- set_ripples_enabled(enabled: bool) None[source]¶
Switch all field ripples independently of mouse gravity and dragging.
- Parameters:
enabled – whether recurring and discrete field waves are enabled.
- Returns:
None.
- 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
ValueErroron 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
spaceoutdressing every request lands on the fractal, whoever asked and for whatever — includingspacr.qt.preferences.apply_ambient_preferences(), which calls this with the stored animation on every settings save. Seedressed().- 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()—QWidgetalready owns that name and it returns aQSize.
- 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.
- property engine: AmbientEngine[source]¶
The live engine. Replaced wholesale by
set_theme().
- class spacr.qt.widgets.ambient.AuroraEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineFolded curtains of vertical rays, rippling along their own length.
Rays rise from an irregular folded lower edge, fan gently toward the sky, and breathe at different rates. A diffuse sheet joins the rays without a flat rectangular top or repeated texture tiles. The frame raster is native at ordinary Detail, within the physical screen-pixel budget, and is returned with independent ownership.
AURORA_BUFFER_EDGEremains the legacy comparison edge; it no longer caps the active buffer.geometry()yields(x, y_bottom, visible_height, brightness)per sampled column of every curtain, in pixels,AURORA_COLUMNS + 1of them per curtain in curtain order. The painter builds its paths from exactly those numbers, so a test that tracks a fold crest throughgeometryis tracking the crest that is on screen.brightnessis the travelling surge. Seeded irregular ray positions and independent continuous length and brightness cycles modulate the sampled sheet, with tapered tops.Roll the aurora’s bands and their drift.
- anchor(curtain: Curtain, height: int) Tuple[float, float][source]¶
(ramp zero, ray length)for one curtain, in pixels.The ramp’s zero is the altitude the emission stops at, so it sits below everything the fold and the tilt can do — that is what keeps every column of the sheet inside its own colour ramp.
- buffer_scale(width: int, height: int) float[source]¶
Report the aurora sampling ratio for explicit detail controls.
- buffer_size(width: int, height: int) Tuple[int, int][source]¶
Sample native display pixels within the actual screen budget.
- curtain_color(curtain: Curtain, quantised: bool = False) PySide6.QtGui.QColor[source]¶
The curtain’s body colour right now.
Always built from the palette’s first colour, wandering up to
AURORA_HUE_BLENDof the way towards one of the others and back. Every curtain shares that body colour on purpose: the body of an aurora is a single emission line — 557.7 nm oxygen — and the palette’s remaining entries are the top and the fringe, which the ramp puts above and below it. Giving curtain two a red body and curtain three a violet one, which is what indexing the palette by curtain would do, is the one thing that stops the whole theme reading as an aurora.- Parameters:
curtain – simulated curtain whose colour index and shimmer phase choose the palette target and its current blend toward it.
quantised – snap the shimmer to
AURORA_HUE_STEPSso the ray tile can be cached.
- fold(curtain: Curtain, u: float, t: float) float[source]¶
Fold displacement at position
ualong the arc, as a fraction of the canvas height.uruns 0..1 from one end of the arc to the other. Each component issin(2*pi*(u - v*t)/lambda): at a fixed time it is a shape inu, and astadvances that shape slides along u atvwhile the arc itself goes nowhere. That is the whole difference between an aurora and a curtain being dragged sideways, and it is the one property of this engine worth testing directly.Scaled by the size setting along with everything else: a curtain half the height with folds the same depth is a different phenomenon, not a smaller one.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
Every travelling wave, evaluated along every arc.
The loop body is
fold()andpulse()written out with their constant parts hoisted —sin(2*pi*(u - v*t)/lambda + phi)issin(k*u + (phi - k*v*t)), andkand the bracket do not depend on the column. It is the same arithmetic; it is here rather than behind those two calls because this runs a hundred and twenty times a frame and a Python call is not free.test_aurora_geometry_ is_the_model_it_documentsholds the two forms together.
- hue_phase(curtain: Curtain) float[source]¶
Where this curtain’s slow colour shimmer stands, in 0..1.
- pulse(curtain: Curtain, u: float, t: float) float[source]¶
The surge’s brightness at
u, in 0..1. Another travelling wave, deliberately faster and shorter than every fold.
- ramp_colors(curtain: Curtain, quantised: bool = False) Dict[str, PySide6.QtGui.QColor][source]¶
The four palette roles for one curtain: the body, the high red, the low fringe, and the overlap between body and high.
Fixed roles rather than a rotation, because the vertical order is physics. With
borealis,mainis the 557.7 nm green,highthe 630.0 nm red,fringethe 427.8 nm violet andblendthe pale yellow-green where the first two overlap. A palette with fewer than four colours reuses what it has.
- ray_lengths(curtain: Curtain) Tuple[float, ...][source]¶
Each ray’s current length, as a fraction of the full one.
One value per entry in
AURORA_TILE_RAYS, quantised intoAURORA_LENGTH_STEPSso the tile stays cacheable – a length that followed the clock exactly would rebuild every tile every frame, which is the cost the tile cache exists to avoid.The periods in
AURORA_RAY_LIFEare deliberately not multiples of one another, and the curtain’s own phase is added, so two curtains never breathe together either.
- class spacr.qt.widgets.ambient.BlobsEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineDiffuse colour blobs, drifting and pulsing.
Motion is two independent sines per blob rather than a random walk, so position is a pure function of the clock: no accumulated error, and
set_timecan jump anywhere.geometry()yields(cx, cy, radius)per blob, in pixels.Start with no buffer – the first shade allocates one.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
The shapes to draw at the current time, for a widget this size.
GEOMETRY, NOT PAINTING, so the layout can be computed on a worker thread and tested without a QPainter – an engine owns no widget and no timer, which is what makes the animation deterministic.
- Parameters:
width – the widget’s width in pixels.
height – its height in pixels.
- Returns:
one tuple per shape, in draw order.
- class spacr.qt.widgets.ambient.BokehEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineDefocused points of light: flat discs with bright rims.
geometry()yields(cx, cy, radius, focus)per disc, in pixels, withfocusin 0..1 — the same tuple the painter builds its gradients from, so a test that asserts on it is asserting on the frame.Start with no buffer – the first shade allocates one.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
The shapes to draw at the current time, for a widget this size.
GEOMETRY, NOT PAINTING, so the layout can be computed on a worker thread and tested without a QPainter – an engine owns no widget and no timer, which is what makes the animation deterministic.
- Parameters:
width – the widget’s width in pixels.
height – its height in pixels.
- Returns:
one tuple per shape, in draw order.
- class spacr.qt.widgets.ambient.CellsEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineCells drifting through the field, turning as they go.
geometry()yields(cx, cy, major, minor, angle)per cell, in pixels and radians.Start with no buffer – the first shade allocates one.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
The shapes to draw at the current time, for a widget this size.
GEOMETRY, NOT PAINTING, so the layout can be computed on a worker thread and tested without a QPainter – an engine owns no widget and no timer, which is what makes the animation deterministic.
- Parameters:
width – the widget’s width in pixels.
height – its height in pixels.
- Returns:
one tuple per shape, in draw order.
- class spacr.qt.widgets.ambient.DriftEngine(*args, **kwargs)[source]¶
Bases:
AmbientEngineA slow starfield in three parallax layers.
At full Detail dots are drawn directly and stay crisp. Lower Detail samples the same canvas population through a bounded image buffer. Everything is batched into one
drawPointscall per (colour, layer, alpha step) bucket with a cached pen; the per-call overhead of a pen change dominates this theme, not the pixels.geometry()yields(x, y, diameter)per painted particle, in pixels.Roll the starfield’s three parallax layers.
- alpha_scale() float[source]¶
Untouched, unlike every buffered theme.
The compensation on the base class exists because overlapping translucent fields pile up additively. A starfield does not have that problem — measured, its mean frame lightness moves from 0.076 to 0.077 across the whole density range, because a couple of hundred dots light 0.65 % of the page and almost never land on each other. Dividing their alpha by three would not un-brighten anything; it would hide two thirds of the stars in the background.
- buffer_size(width: int, height: int) Tuple[int, int][source]¶
Sample dots at the chosen Detail within the display pixel ceiling.
- count_for(width: int, height: int) int[source]¶
How many of the pool this canvas gets.
Area-based, so a small window is not a snowstorm, and then scaled by the density setting — which is the only one of the two the user controls.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
(x, y, diameter)per painted particle, in pixels.Three directions, one expression each, and all three are pure functions of the clock — no accumulated state, so
set_timestill jumps anywhere and two engines on the same seed still agree.upanddownare the same shared vector with opposite signs.randomgives every speck its own heading and adds a slow wander across it, which is a smooth wandering path rather than a straight line with a wobble; the headings are isotropic, so the field spreads and mixes instead of travelling.
- halo_size(layer: int) float[source]¶
Diameter of the soft pass around a dot. Equal to the dot itself at blur 0, which is how the default frame stays exactly what it was.
- class spacr.qt.widgets.ambient.Form[source]¶
Bases:
NamedTupleSampling geometry for the primary fractal or a derived bud.
- Parameters:
cx – Centre x-coordinate in buffer pixels.
cy – Centre y-coordinate in buffer pixels.
scale – Buffer pixels per unit of the complex plane.
angle – Sampling rotation in radians.
c_re – Real component of the Julia-set constant.
c_im – Imaginary component of the Julia-set constant.
iterations – Number of map iterations.
origin_re – Real component of the sampled complex-plane origin.
origin_im – Imaginary component of the sampled origin.
swirl – Angular twist per logarithmic radius interval.
tunnel – Palette traversals per logarithmic radius interval.
scroll – Palette-ring offset.
radius – Form radius in buffer pixels.
age – Normalised bud age; zero for the primary form.
bud – Whether this geometry describes a bud.
- class spacr.qt.widgets.ambient.FractalEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineRender a deterministic, animated Julia-set field.
The engine evaluates
z <- z**2 + cfor a primary form and optional derived buds. Hashed continuous state controls depth, density, motion, and population as a pure function of seed and animation time. Render cost is measured between frames and used to reduce buffer resolution and bud count when necessary.geometry()returns the primaryFormfirst.Start with no buffer – the first shade allocates one.
- buds(width: int, height: int) Tuple[Form, ...][source]¶
Return active bud geometries for a render-buffer size.
- constant() Tuple[float, float][source]¶
Return the current Julia-set constant as
(real, imaginary).The constant follows an inset path along the main cardioid, with continuous pace modulation.
- fixed_point() Tuple[float, float][source]¶
Return the repelling fixed point of the current Julia set.
The selected root is
(1 + sqrt(1 - 4c)) / 2with|2 * beta| > 1.
- geometry(width: int, height: int) Tuple[Form, ...][source]¶
Return frame geometries with the primary form first.
- class spacr.qt.widgets.ambient.Motion[source]¶
Bases:
NamedTupleEvery 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.
- class spacr.qt.widgets.ambient.PaletteSpec[source]¶
Bases:
NamedTupleOne named colour set: what to call it and what it is made of.
- class spacr.qt.widgets.ambient.ResonanceEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineChladni figures: sand on a plate that is driven by what is playing.
A floor with particles on it, and the particles are sand on a vibrating plate. The physics is in
spacr.qt.resonanceand is worth one paragraph here because it is what makes this theme different from a particle system with a music-shaped wobble: a plate driven at one of its resonances has lines that do not move, sand is thrown off everywhere else and comes to rest along them, and the figure IS the nodal set. So there is a right answer for where a particle goes, and the drive decides which figure it is and how hard the sand is being shaken rather than deciding the motion directly.WHAT DRIVES IT.
spacr.qt.resonance.playing_moment()– the music bed’s own precomputed envelope and spectrum, read at the position the bed is at. Nothing captures the machine’s audio; nothing here can hear anything spaCR is not playing. With sound off it returns silence and the plate idles on its own clock, which is the case almost everybody sees because sound is off on a fresh install.WHY THE DRIVE IS READ IN
advance()AND NOT WHERE IT IS USED. Every other engine promises that a frame is a pure function of(seed, clock, size), and two tests hold it down: one shades the same clock on two threads and compares the bytes, another steps one engine twelve times and jumps a second straight to the same clock. Reading a real-time signal insideshade()would break both, and it would break them on the SHADING THREAD, where the failure is a picture that differs from the one the GUI thread would have drawn. So the drive is an INPUT, set on the GUI thread between frames under the engine lock exactly asset_palette()is, and the promise becomes “a pure function of(seed, clock, size, drive)” – which is the same promise while nothing is playing, and that is when the tests run.geometry()yields(x, y, brightness)per painted particle, in pixels.Start with no floor and no canvas; the first shade builds both.
- advance(dt: float) None[source]¶
Step the clock and read what is playing.
The one place a real-time signal enters this engine, and it is here because this runs on the GUI thread between frames while the shading thread is locked out – see the class docstring. It costs a lock, an index and two array reads; measured at 6.8 us with the music bed playing, against the 41 ms a frame the cap allows.
What is read is damped by
answering()before it is stored, so the Speed preference reaches the music-driven half of the theme as well as the clock.
- answering(moment)[source]¶
momentscaled by how much of it the Speed setting lets in.THE CLOCK IS NOT THE WHOLE ANIMATION HERE, WHICH IS WHY THIS EXISTS.
AmbientEngine.advance()multipliesdtbyspeed, and that is the whole of the Speed preference for every other theme, because every other theme’s motion is a function of the clock alone. Half of this one is a function of the music instead: the throw on an onset, the per-band brightness and the figure the spectrum’s centre of mass asks for. At the bottom ofSPEED_RANGEthe breath, the mode walk and the wander all crawl at a tenth, and a kick would still have thrown the sandRESONANCE_THROWof the plate twice a second – leaving the busiest movement in the theme running at full rate for somebody who set the slider to its minimum precisely to stop that.So the share let in is
min(1, speed): the shipped setting and anything above it hear the music in full, and turning the animation down turns the reaction down with it, until at the minimum the plate is the idle plate. Above 1.0 it is not scaled up, because every field of aMomentis already 0 to 1 against the loop’s own loudest: there is nothing above full to give.- Parameters:
moment – what
spacr.qt.resonance.playing_moment()returned.- Returns:
the same moment at speed 1.0 or above, a damped copy below it.
- energy() float[source]¶
How hard the plate is being driven, 0 to 1.
The louder of what is playing and the idle breath, so a quiet passage never takes the picture below what silence would have drawn. That is the rule that makes “it idles beautifully in silence” and “it reacts to the music” the same code path.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
Every grain as
(x, y, brightness)in pixels.- Parameters:
width – canvas width in pixels.
height – canvas height.
- Returns:
one tuple per painted grain.
- plate(width: int, height: int) Tuple[float, float, float][source]¶
The square the figure is drawn in:
(left, top, side)in px.
- sand(count: int | None = None)[source]¶
Where the sand is now, and how brightly each grain shows.
The whole simulation, and it is four numpy passes: wander the seeded starting points, build the lattice for the mode the drive asks for, relax onto its nodal lines, and throw the result off them by whatever the last onset was worth.
- Parameters:
count – how many grains; the density setting’s own count by default.
- Returns:
(x, y, brightness)in plate units, x and y in 0..1.
- class spacr.qt.widgets.ambient.RippleEngine(*args, **kwargs)[source]¶
Bases:
_BufferedEngineConcentric rings expanding from a few sources and fading as they grow.
Each ring is a radial gradient annulus rather than a stroked circle: a stroked one is line work, which is both expensive and far too crisp for something meant to sit behind a settings form.
geometry()yields(cx, cy, radius, fade)per ring, in pixels, withfadein 0..1 — 0 as the ring is born and as it dies at the edge of its reach, 1 halfway.Start with no buffer – the first shade allocates one.
- geometry(width: int, height: int) Tuple[tuple, ...][source]¶
The shapes to draw at the current time, for a widget this size.
GEOMETRY, NOT PAINTING, so the layout can be computed on a worker thread and tested without a QPainter – an engine owns no widget and no timer, which is what makes the animation deterministic.
- Parameters:
width – the widget’s width in pixels.
height – its height in pixels.
- Returns:
one tuple per shape, in draw order.
- 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_ANIMATIONor a paintable theme name; any other name raisesValueError.
- 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_ANIMATIONor a paintable theme name; any other name raisesValueError.
- spacr.qt.widgets.ambient.coerce_palette(theme: str, palette: str) str[source]¶
paletteifthemeoffers it, else that theme’s default.Only for stored values — a preferences file that still names the palette the user picked under a different theme should not stop a screen from being built. An unknown name is still an error: it is a bug, not a stale setting.
- spacr.qt.widgets.ambient.default_palette_for(theme: str) str[source]¶
The palette
themefalls back to —DEFAULT_PALETTEwhen 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. A Spaceout launch uses its own stored selection, or defaults to the evolving field before one is saved.
- Parameters:
theme – the requested theme name, 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 raisesValueError.
- 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 raisesValueError.
- spacr.qt.widgets.ambient.field_ripple_for_widget(widget, edge=None, rect=None, strength=1.0) None[source]¶
Publish settled window or panel feedback to its visible field backdrop.
- Parameters:
widget – the resized, moved or collapsed widget.
edge – an optional snapped window side.
rect – optional final rectangle in widget-local coordinates.
strength – positive enables release feedback; zero disables it.
- Returns:
None.
- 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 untilhostis 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 frombluron 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, whichis_valid_theme()rejects because it cannot be painted.- Parameters:
name – the value to test, typically a stored preference.
- spacr.qt.widgets.ambient.is_dark_background(color: PySide6.QtGui.QColor | str) bool[source]¶
True when
coloris dark enough for additive compositing.
- spacr.qt.widgets.ambient.is_valid_drift_direction(name) bool[source]¶
True when
nameis one ofDRIFT_DIRECTIONS. Never raises.- Parameters:
name – the value to test.
- spacr.qt.widgets.ambient.is_valid_palette(theme, palette) bool[source]¶
True when
paletteis offered bytheme. 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
nameis one ofAMBIENT_THEMES.The predicate exists so a caller validating stored preferences does not have to catch
ValueErrorfrom the strict accessors below.- Parameters:
name – the value to test, typically a stored preference.
- spacr.qt.widgets.ambient.make_engine(theme: str, palette: str, background: PySide6.QtGui.QColor | str, seed: int | None = None, blur: float = DEFAULT_BLUR, speed: float = DEFAULT_SPEED, size: float = DEFAULT_SIZE, resolution: float = DEFAULT_RESOLUTION, density: float = DEFAULT_DENSITY, direction: str = DEFAULT_DRIFT_DIRECTION, blink_percent: float = DEFAULT_BLINK_PERCENT, popup_wave_frequency: float = 0.0) AmbientEngine[source]¶
Build the engine for
theme/palette. Raises on unknown names.Everything after
seedis a user control; the defaults are the shipped animation exactly.
- spacr.qt.widgets.ambient.palette_colors(theme: str, palette: str) Tuple[str, ...][source]¶
The
#rrggbbcolours behindpalette, fortheme.- Parameters:
theme – a paintable theme name.
palette – a palette
themeoffers; an unknown theme, or a palette the theme does not offer, raisesValueError.
- spacr.qt.widgets.ambient.palette_label(theme: str, palette: str) str[source]¶
Human label for
paletteas offered bytheme.- Parameters:
theme – a paintable theme name.
palette – a palette
themeoffers; an unknown theme, or a palette the theme does not offer, raisesValueError.
- 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
themeoffers; an unknown theme, or a palette the theme does not offer, raisesValueError.
- spacr.qt.widgets.ambient.palettes_for(theme: str) Tuple[str, ...][source]¶
The palette names
themeoffers, in menu order.Never empty. Raises
ValueErroron 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.rebuild_the_spaceout_backdrops() int[source]¶
Rebuild every running spaceout backdrop whose build settings changed.
- Returns:
how many were rebuilt.
WHY A SAVED PATTERN USED TO WAIT FOR A RESTART. The window builds ONE backdrop behind the dock and the page and keeps it for the session. Saving Preferences pushed the runtime numbers into it (
apply_saved_controls) but the pattern, backend, quality and scale are fixed when it is constructed, and nothing constructed it again – the old note that “changing a screen” would do it stopped being true when the backdrop moved from the screens to the window.The replacement is built BEFORE the old one is retired, so a GPU that cannot be had right now leaves the running backdrop on screen rather than none. It inherits the old one’s
RuntimeControls, its paused state and its visibility, and every reference to the old one on its host or window is moved to it. A heavy import holding the GL lock is waited out on a timer, as at startup.
- spacr.qt.widgets.ambient.screen_pixels(widget: PySide6.QtWidgets.QWidget | None = None) int[source]¶
Return the device-pixel count of the screen containing
widget.The primary screen is used when no widget screen is available. Headless or invalid screen information falls back to
BUFFER_MAX_PIXELS.
- 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.