Say at the Record button why Record did nothing
Fixes #316: in a new session, clicking Record did nothing visible. With no record-enabled track there is nothing to record, and the reason went only to the end of the dim status line at the bottom of the Edit window. In a new session it also pointed at "the red button in its header", which no track existed to show. - Record with nothing to record on says what to do: add a track with Track › New… when the session has none, else click R on the track to record. - The message shows for 4 seconds below the toolbar's Record button (under the whole transport group, so it covers none of its buttons) and under the Transport window's Record button, as well as in the status line. It goes early once a track is record-enabled. - Nothing is armed or created on the user's behalf, and the transport stays stopped.
This commit is contained in:
parent
6cf896e76c
commit
0a5fc6ccba
|
|
@ -259,6 +259,8 @@ pub struct SoundApp {
|
|||
pub(crate) docking_context: Option<egui::Context>,
|
||||
/// Transport simulation when there is no player (tests, offscreen renders).
|
||||
sim: Option<(Samples, Option<Samples>, Option<Range>)>,
|
||||
/// When Record last found nothing to record on, and why: the Record buttons show it for a moment.
|
||||
record_hint: Option<(f64, &'static str)>,
|
||||
pub quit_requested: bool,
|
||||
/// Reset floating-window positions next frame (Window › Arrange).
|
||||
pub arrange_request: bool,
|
||||
|
|
@ -318,6 +320,7 @@ impl SoundApp {
|
|||
theme_applied: None,
|
||||
docking_context: None,
|
||||
sim: None,
|
||||
record_hint: None,
|
||||
quit_requested: false,
|
||||
arrange_request: false,
|
||||
touched: std::collections::HashSet::new(),
|
||||
|
|
@ -369,6 +372,13 @@ impl SoundApp {
|
|||
self.engine.transport.playing
|
||||
}
|
||||
|
||||
/// Why the last Record press did nothing, for a few seconds and while it still holds.
|
||||
pub(crate) fn record_hint(&self) -> Option<&'static str> {
|
||||
let (at, why) = self.record_hint?;
|
||||
let fresh = self.last_frame.unwrap_or_default() - at < 4.0;
|
||||
(fresh && !self.engine.session().tracks.iter().any(|t| t.mixer.record_arm)).then_some(why)
|
||||
}
|
||||
|
||||
/// Current transport position (playhead while playing, else the edit insertion).
|
||||
pub fn position(&self) -> Samples {
|
||||
if self.engine.transport.playing { self.engine.transport.position } else { self.engine.session().edit.selection.start }
|
||||
|
|
@ -435,7 +445,13 @@ impl SoundApp {
|
|||
fn start_recording(&mut self) {
|
||||
let armed = self.engine.session().tracks.iter().any(|t| t.mixer.record_arm);
|
||||
if !armed {
|
||||
self.ui.status = "Record-enable a track first (the red button in its header).".into();
|
||||
let why = if self.engine.session().tracks.is_empty() {
|
||||
"Nothing to record on yet: add a track with Track › New…, then click its R button."
|
||||
} else {
|
||||
"No track is record-enabled: click the R button on the track to record."
|
||||
};
|
||||
self.ui.status = why.into();
|
||||
self.record_hint = Some((self.last_frame.unwrap_or_default(), why));
|
||||
return;
|
||||
}
|
||||
self.ensure_input();
|
||||
|
|
@ -998,6 +1014,29 @@ mod tests {
|
|||
assert_eq!(app.engine.transport.position, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_with_nothing_armed_says_what_to_do() {
|
||||
let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default());
|
||||
app.run("transport.record", json!({})).unwrap();
|
||||
app.handle_transport(0.25);
|
||||
assert!(!app.is_playing() && !app.engine.transport.recording);
|
||||
assert!(app.record_hint().is_some_and(|why| why.contains("Track › New")), "{:?}", app.record_hint());
|
||||
|
||||
app.run("track.new", json!({"count": 1, "format": "mono"})).unwrap();
|
||||
app.run("transport.record", json!({})).unwrap();
|
||||
app.handle_transport(0.25);
|
||||
assert!(app.record_hint().is_some_and(|why| why.contains("R button")), "{:?}", app.record_hint());
|
||||
assert_eq!(Some(app.ui.status.as_str()), app.record_hint());
|
||||
app.last_frame = Some(5.0);
|
||||
assert_eq!(app.record_hint(), None, "the hint shows for a few seconds");
|
||||
|
||||
app.last_frame = None;
|
||||
app.run("transport.record", json!({})).unwrap();
|
||||
app.handle_transport(0.25);
|
||||
app.run("mix.record_arm", json!({})).unwrap();
|
||||
assert_eq!(app.record_hint(), None, "arming a track answers the hint");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hidden_window_still_autosaves() {
|
||||
let dir = std::env::temp_dir().join(format!("soundcraft-hidden-autosave-{}", std::process::id()));
|
||||
|
|
|
|||
|
|
@ -198,15 +198,19 @@ fn transport_window(app: &mut SoundApp, ctx: &egui::Context) {
|
|||
("end", "transport.go_to_end"),
|
||||
("record", "transport.record"),
|
||||
] {
|
||||
if crate::widgets::icon_button(
|
||||
let resp = crate::widgets::icon_button(
|
||||
ui,
|
||||
vec2(34.0, 26.0),
|
||||
icon,
|
||||
(cmd == "transport.play" && app.is_playing()) || (cmd == "transport.record" && app.engine.transport.recording),
|
||||
cmd,
|
||||
)
|
||||
.clicked()
|
||||
);
|
||||
if cmd == "transport.record"
|
||||
&& let Some(why) = app.record_hint()
|
||||
{
|
||||
egui::Tooltip::for_widget(&resp).show(|ui| ui.label(why));
|
||||
}
|
||||
if resp.clicked() {
|
||||
let _ = app.run(cmd, json!({}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ fn transport(app: &mut SoundApp, ui: &mut Ui) {
|
|||
let playing = app.is_playing();
|
||||
let recording = app.engine.transport.recording;
|
||||
let looped = app.engine.session().edit.loop_playback;
|
||||
group(ui, 176.0, 64.0, |ui, _| {
|
||||
group(ui, 176.0, 64.0, |ui, g| {
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(3.0, 4.0);
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -328,7 +328,13 @@ fn transport(app: &mut SoundApp, ui: &mut Ui) {
|
|||
StrokeKind::Inside,
|
||||
);
|
||||
icons::draw(ui.painter(), r.shrink(5.0), "record", t.rec);
|
||||
if resp.on_hover_text("Record (Cmd+Space)").clicked() {
|
||||
let resp = resp.on_hover_text("Record (Cmd+Space)");
|
||||
if let Some(why) = app.record_hint() {
|
||||
// Below the whole group, so the hint covers none of its buttons.
|
||||
let below = Rect::from_min_max(r.min, pos2(r.max.x, g.max.y));
|
||||
egui::Tooltip::always_open(ui.ctx().clone(), resp.layer_id, resp.id, below).show(|ui| ui.label(why));
|
||||
}
|
||||
if resp.clicked() {
|
||||
let _ = app.run("transport.record", json!({}));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
//! Record with nothing to record on explains itself at the Record buttons.
|
||||
|
||||
use egui::vec2;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use soundcraft_ui_egui::{Services, SoundApp};
|
||||
|
||||
#[test]
|
||||
fn record_in_an_empty_session_shows_why_at_both_record_buttons() {
|
||||
let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default());
|
||||
app.ui.show_transport = true;
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state(
|
||||
|ui, app: &mut SoundApp| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(5);
|
||||
assert_eq!(h.query_all_by_label_contains("Nothing to record on").count(), 0);
|
||||
h.get_by_label("transport.record").click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().engine.transport.recording);
|
||||
assert_eq!(h.query_all_by_label_contains("Nothing to record on").count(), 2, "the toolbar's and the Transport window's");
|
||||
}
|
||||
Loading…
Reference in New Issue