From 718dccdad7779eb47802c989a8226bcafeb727af Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 5 Oct 2026 23:50:24 -0400 Subject: [PATCH] Transport window: counters, expanded pre/post-roll, MIDI controls, output meters Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels.rs | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 9bf7400..c4a891b 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -135,7 +135,10 @@ fn transport_window(app: &mut SoundApp, ctx: &egui::Context) { if !open { return; } + let t = Tokens::DARK; egui::Window::new("Transport").open(&mut open).resizable(false).default_pos(pos2(400.0, 500.0)).show(ctx, |ui| { + let flags = app.engine.session().edit.flags.clone(); + let expanded = flags.contains("view.transport.expanded"); ui.horizontal(|ui| { for (icon, cmd) in [ ("rtz", "transport.rtz"), @@ -146,14 +149,68 @@ fn transport_window(app: &mut SoundApp, ctx: &egui::Context) { ("end", "transport.go_to_end"), ("record", "transport.record"), ] { - if crate::widgets::icon_button(ui, vec2(34.0, 26.0), icon, false, cmd).clicked() { + if 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() + { let _ = app.run(cmd, json!({})); } } + if flags.contains("view.transport.output_meters") { + let (r, _) = ui.allocate_exact_size(vec2(22.0, 26.0), Sense::hover()); + let m = app.main_meter; + crate::widgets::meter(ui, Rect::from_min_size(r.min, vec2(10.0, 26.0)), m.level[0], m.hold[0], m.clip); + crate::widgets::meter(ui, Rect::from_min_size(pos2(r.min.x + 12.0, r.min.y), vec2(10.0, 26.0)), m.level[1], m.hold[1], m.clip); + } }); let s = app.engine.session(); - let txt = format_position(app.position(), s.edit.main_counter, s.sample_rate, &s.tempo, s.frame_rate, s.timecode_start); - ui.label(egui::RichText::new(txt).font(mono(20.0)).color(Tokens::DARK.counter_text)); + let fmt = |x: i64| format_position(x, s.edit.main_counter, s.sample_rate, &s.tempo, s.frame_rate, s.timecode_start); + if flags.contains("view.transport.counters") || expanded || flags.is_empty() { + ui.label(egui::RichText::new(fmt(app.position())).font(mono(20.0)).color(t.counter_text)); + } + if expanded { + let e = s.edit.clone(); + egui::Grid::new("tx_exp").num_columns(4).show(ui, |ui| { + ui.label("Start"); + ui.label(egui::RichText::new(fmt(e.selection.start)).font(mono(11.0))); + ui.label("Pre-roll"); + ui.label(egui::RichText::new(format!("{:.2} s", s.sample_rate.seconds(e.pre_roll))).font(mono(11.0))); + ui.end_row(); + ui.label("End"); + ui.label(egui::RichText::new(fmt(e.selection.end)).font(mono(11.0))); + ui.label("Post-roll"); + ui.label(egui::RichText::new(format!("{:.2} s", s.sample_rate.seconds(e.post_roll))).font(mono(11.0))); + ui.end_row(); + }); + } + if flags.contains("view.transport.midi_controls") || expanded { + let (click, countoff, merge, loop_rec, prepost) = { + let e = &app.engine.session().edit; + (e.click, e.countoff, e.midi_merge, e.loop_record, e.pre_post_roll) + }; + ui.horizontal(|ui| { + for (label, on, cmd) in [ + ("Click", click, "options.click"), + ("Count Off", countoff, "options.countoff"), + ("MIDI Merge", merge, "options.midi_merge"), + ("Loop Rec", loop_rec, "options.loop_record"), + ("Pre/Post", prepost, "options.pre_post_roll"), + ] { + if ui.selectable_label(on, label).clicked() { + let _ = app.run(cmd, json!({})); + } + } + }); + let tick = app.engine.session().tempo.samples_to_ticks(app.position(), app.engine.session().sample_rate); + let bpm = app.engine.session().tempo.tempo_at_tick(tick); + let m = app.engine.session().tempo.meter_at_tick(tick); + ui.label(egui::RichText::new(format!("♩ = {bpm:.2} {}/{}", m.numerator, m.denominator)).font(mono(12.0)).color(t.counter_text)); + } }); app.ui.show_transport = open; }