Skip to content

Commit 39b37e5

Browse files
committed
feat: add no-notifications mode
Suppress transient toasts and ambient parameter readouts for a clean field. Start with --no-notifications; toggle live with Ctrl+N. Guards push_msg and the ambient Tune surface; user-opened overlays (controls, palette editor, dashboard, preset transitions) are unaffected. Listed in the README controls table and the ? hints overlay. Recapture the palette, charset, and randomize gallery demos with notifications suppressed.
1 parent 3c67f6f commit 39b37e5

10 files changed

Lines changed: 75 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ First public release.
2020
- README gallery: six looping demos of the runtime controls — palette cycling,
2121
character sets, preset transitions, randomize, live parameter tuning, and the
2222
palette editor (replaces the earlier static preset stills)
23+
- No-notifications mode: suppress transient toasts and ambient parameter
24+
readouts for a clean field. Start with `--no-notifications`; toggle live with
25+
`Ctrl+N`. User-opened overlays (controls, palette editor, dashboard, preset
26+
transitions) are unaffected.
2327
- `vinescii` preset — the vines (flocking) pattern in pure ASCII
2428
- Experimental: multi-species, choir audio, and GUI (feature-gated); WASM build (standalone crate)
2529

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ lowercase increases and uppercase (Shift) decreases.
120120
| `Ctrl+Z` / `Ctrl+Y` | Undo / redo |
121121
| `h` | Controls panel |
122122
| `?` | Keyboard hints |
123+
| `Ctrl+N` | Toggle notifications (toasts + param readouts) |
123124
| `\` | Dashboard overlay |
124125
| `g` | Save current frame to PNG |
125126
| `q` | Quit |

assets/demos/charset-cycle.gif

217 KB
Loading

assets/demos/palette-cycle.gif

-177 KB
Loading

assets/demos/randomize.gif

87.9 KB
Loading

src/app/runner.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,7 @@ pub fn run_simulation(
10201020
runtime_state.active_overrides = startup_ov;
10211021
runtime_state.preload_pause_logo(term_width as usize, term_height as usize);
10221022
runtime_state.dither_mode = dither_mode;
1023+
runtime_state.notifications_enabled = !args.no_notifications;
10231024
runtime_state.trail_age_enabled = args.trail_age;
10241025
runtime_state.trail_delta_enabled = args.trail_delta;
10251026
runtime_state.gradient_magnitude_enabled = args.gradient_magnitude;
@@ -3305,6 +3306,15 @@ pub fn run_simulation(
33053306
runtime_state.on_modal_close();
33063307
}
33073308
}
3309+
ControlAction::ToggleNotifications => {
3310+
runtime_state.notifications_enabled =
3311+
!runtime_state.notifications_enabled;
3312+
// Confirm only when turning them back ON — push_msg is
3313+
// a no-op while disabled, so an "off" toast can't show.
3314+
if runtime_state.notifications_enabled {
3315+
runtime_state.show_notification("Notifications on".to_string());
3316+
}
3317+
}
33083318
ControlAction::SetIntensityMapping(_) => {}
33093319
ControlAction::ShowConfigBrowser => {
33103320
runtime_state.close_all_overlays();
@@ -3535,9 +3545,12 @@ pub fn run_simulation(
35353545
// each adjust refreshes the hold so rapid adjusts extend it,
35363546
// then it eases back to BASE. This REPLACES any on-adjust
35373547
// toast (none existed — param adjusts only notify at bounds).
3538-
if let Some(tune) =
3548+
let tune_view = if runtime_state.notifications_enabled {
35393549
tune_view_for_action(&runtime_state, &action, &registry_ctx, agent_count)
3540-
{
3550+
} else {
3551+
None
3552+
};
3553+
if let Some(tune) = tune_view {
35413554
const TUNE_HOLD_SECS: f32 = 2.5;
35423555
let now = runtime_state.phase_clock;
35433556
// Surface the focused param (debounced) and drop the

src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,13 @@ pub struct Args {
852852
/// Show the preset tagline during figlet/type transitions.
853853
pub transition_tagline: bool,
854854

855+
#[arg(
856+
long = "no-notifications",
857+
help = "Suppress transient notifications (toasts + ambient param readouts); toggle at runtime with Ctrl+N"
858+
)]
859+
/// Start with transient notifications suppressed. Toggle live with `Ctrl+N`.
860+
pub no_notifications: bool,
861+
855862
#[arg(
856863
long = "aspect",
857864
value_name = "RATIO",
@@ -2354,6 +2361,7 @@ impl Default for Args {
23542361
chrome_style: None,
23552362
transition: None,
23562363
transition_tagline: false,
2364+
no_notifications: false,
23572365
aspect: None,
23582366
window_padding: None,
23592367
frame_matte_cols: None,

src/render/overlay.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ impl KeyboardHintsOverlay {
269269
Left,
270270
Left,
271271
)
272+
.add_two_col(
273+
"Ctrl+N Notifications",
274+
"g Save frame PNG",
275+
Left,
276+
Left,
277+
)
272278
.add_two_col(
273279
"( / ) Window frame",
274280
"F10 Cycle chrome",

src/terminal/input.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub fn handle_key_event(key_event: &KeyEvent) -> ControlAction {
126126
}
127127
}
128128
KeyCode::Char('y') | KeyCode::Char('Y') => return ControlAction::Redo,
129+
KeyCode::Char('n') | KeyCode::Char('N') => return ControlAction::ToggleNotifications,
129130
_ => {}
130131
}
131132
}

src/terminal/state.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,8 @@ pub enum ControlAction {
371371
CycleOptionsCategoryReverse,
372372
/// Toggle dashboard overlay (merged stats + info).
373373
ToggleDashboard,
374+
/// Toggle transient notifications (toasts + ambient param readouts).
375+
ToggleNotifications,
374376
/// Show configuration browser.
375377
ShowConfigBrowser,
376378
/// Show configuration save dialog.
@@ -813,6 +815,12 @@ pub struct RuntimeState {
813815
/// runner resolves the highest-priority live entry each frame via
814816
/// [`crate::render::ambient::resolve`].
815817
pub ambient_states: Vec<crate::render::ambient::AmbientState>,
818+
/// When `false`, transient notifications are suppressed: toast messages
819+
/// ([`push_msg`](Self::push_msg)) and the ambient `Tune` param readouts are
820+
/// not surfaced, leaving the field clean. User-opened overlays (controls,
821+
/// palette editor, dashboard, preset-transition) are unaffected. Toggled at
822+
/// runtime with `Ctrl+N`; set false at launch via `--no-notifications`.
823+
pub notifications_enabled: bool,
816824
}
817825

818826
impl RuntimeState {
@@ -963,6 +971,7 @@ impl RuntimeState {
963971
glyph: crate::render::charset::GlyphConfig::default(),
964972
phase_clock: 0.0,
965973
ambient_states: vec![crate::render::ambient::AmbientState::Base],
974+
notifications_enabled: true,
966975
}
967976
}
968977

@@ -1873,6 +1882,11 @@ impl RuntimeState {
18731882
/// At most one active MSG is kept. The duration is determined by `level`
18741883
/// via [`crate::render::ambient::msg`].
18751884
pub fn push_msg(&mut self, level: NotificationLevel, text: String) {
1885+
// Suppressed entirely when notifications are off, so no transient toast
1886+
// ever reaches the ambient stack.
1887+
if !self.notifications_enabled {
1888+
return;
1889+
}
18761890
let now = self.phase_clock;
18771891
// Remove any existing Msg entries so messages don't stack unbounded.
18781892
self.ambient_states
@@ -2064,6 +2078,32 @@ mod tests {
20642078
)
20652079
}
20662080

2081+
#[test]
2082+
fn notifications_enabled_by_default_and_push_msg_surfaces_a_toast() {
2083+
let mut rs = create_test_runtime_state();
2084+
assert!(rs.notifications_enabled, "default should be enabled");
2085+
rs.push_msg(NotificationLevel::Info, "hello".to_string());
2086+
assert!(
2087+
rs.ambient_states
2088+
.iter()
2089+
.any(|s| matches!(s, crate::render::ambient::AmbientState::Msg { .. })),
2090+
"a Msg should be surfaced when notifications are enabled"
2091+
);
2092+
}
2093+
2094+
#[test]
2095+
fn disabled_notifications_suppress_push_msg() {
2096+
let mut rs = create_test_runtime_state();
2097+
rs.notifications_enabled = false;
2098+
rs.push_msg(NotificationLevel::Info, "hidden".to_string());
2099+
assert!(
2100+
!rs.ambient_states
2101+
.iter()
2102+
.any(|s| matches!(s, crate::render::ambient::AmbientState::Msg { .. })),
2103+
"no Msg should be surfaced when notifications are disabled"
2104+
);
2105+
}
2106+
20672107
#[test]
20682108
fn reset_transient_clears_transient_flags() {
20692109
let mut rs = create_test_runtime_state();

0 commit comments

Comments
 (0)