Skip to content

Commit 57dcdbd

Browse files
committed
feat: enhance UI components with new widgets and improved command handling
1 parent 090e270 commit 57dcdbd

File tree

15 files changed

+592
-62
lines changed

15 files changed

+592
-62
lines changed

apps/egui_ui/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ path = "src/main.rs"
99

1010
[dependencies]
1111
rysyn_ffi_bridge = { path = "../../crates/ffi_bridge" }
12-
rysyn_project = { path = "../../crates/project" }
1312

1413
eframe.workspace = true
1514
egui.workspace = true
@@ -20,6 +19,9 @@ serde_json.workspace = true
2019
log.workspace = true
2120
env_logger.workspace = true
2221

22+
# For browser panel
23+
dirs = "5"
24+
2325
# For audio file preview/waveform (optional)
2426
# hound = "3.5"
2527

apps/egui_ui/src/app.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use eframe::egui;
44
use rysyn_ffi_bridge::{init_bridge, get_bridge, Command, StateSnapshot};
5+
use std::sync::mpsc::{channel, Sender, Receiver};
56

67
use crate::panels::{
78
transport::TransportPanel,
@@ -17,6 +18,10 @@ pub struct RysynApp {
1718
/// Current state from audio core
1819
state: StateSnapshot,
1920

21+
/// Command sender for UI -> Audio Core
22+
cmd_tx: Sender<Command>,
23+
cmd_rx: Receiver<Command>,
24+
2025
/// Panel states
2126
transport: TransportPanel,
2227
timeline: TimelinePanel,
@@ -40,8 +45,12 @@ impl RysynApp {
4045
// Initialize FFI bridge
4146
init_bridge();
4247

48+
let (cmd_tx, cmd_rx) = channel();
49+
4350
Self {
4451
state: StateSnapshot::new(),
52+
cmd_tx,
53+
cmd_rx,
4554
transport: TransportPanel::default(),
4655
timeline: TimelinePanel::default(),
4756
track_list: TrackListPanel::default(),
@@ -56,9 +65,11 @@ impl RysynApp {
5665
}
5766
}
5867

59-
fn send_command(&self, cmd: Command) {
68+
fn process_commands(&self) {
6069
if let Some(bridge) = get_bridge() {
61-
let _ = bridge.send_command(cmd);
70+
while let Ok(cmd) = self.cmd_rx.try_recv() {
71+
let _ = bridge.send_command(cmd);
72+
}
6273
}
6374
}
6475

@@ -74,23 +85,30 @@ impl eframe::App for RysynApp {
7485
// Refresh state from audio core
7586
self.refresh_state();
7687

88+
// Process any pending commands
89+
self.process_commands();
90+
7791
// Request continuous repaint for smooth animation
7892
ctx.request_repaint();
7993

94+
// Clone cmd_tx for use in closures
95+
let cmd_tx = self.cmd_tx.clone();
96+
8097
// === Menu Bar ===
8198
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
99+
let cmd_tx = cmd_tx.clone();
82100
egui::menu::bar(ui, |ui| {
83101
ui.menu_button("File", |ui| {
84102
if ui.button("New Project").clicked() {
85-
self.send_command(Command::NewProject);
103+
let _ = cmd_tx.send(Command::NewProject);
86104
ui.close_menu();
87105
}
88106
if ui.button("Open Project...").clicked() {
89107
// TODO: File dialog
90108
ui.close_menu();
91109
}
92110
if ui.button("Save Project").clicked() {
93-
self.send_command(Command::SaveProject { path: None });
111+
let _ = cmd_tx.send(Command::SaveProject { path: None });
94112
ui.close_menu();
95113
}
96114
ui.separator();
@@ -101,32 +119,32 @@ impl eframe::App for RysynApp {
101119

102120
ui.menu_button("Edit", |ui| {
103121
if ui.button("Undo").clicked() {
104-
self.send_command(Command::Undo);
122+
let _ = cmd_tx.send(Command::Undo);
105123
ui.close_menu();
106124
}
107125
if ui.button("Redo").clicked() {
108-
self.send_command(Command::Redo);
126+
let _ = cmd_tx.send(Command::Redo);
109127
ui.close_menu();
110128
}
111129
});
112130

113131
ui.menu_button("Track", |ui| {
114132
if ui.button("Add Audio Track").clicked() {
115-
self.send_command(Command::CreateTrack {
133+
let _ = cmd_tx.send(Command::CreateTrack {
116134
name: "Audio".to_string(),
117135
is_midi: false
118136
});
119137
ui.close_menu();
120138
}
121139
if ui.button("Add MIDI Track").clicked() {
122-
self.send_command(Command::CreateTrack {
140+
let _ = cmd_tx.send(Command::CreateTrack {
123141
name: "MIDI".to_string(),
124142
is_midi: true
125143
});
126144
ui.close_menu();
127145
}
128146
if ui.button("Add Instrument Track").clicked() {
129-
self.send_command(Command::CreateTrack {
147+
let _ = cmd_tx.send(Command::CreateTrack {
130148
name: "Instrument".to_string(),
131149
is_midi: true
132150
});
@@ -159,67 +177,76 @@ impl eframe::App for RysynApp {
159177
});
160178
});
161179

180+
// Clone state for panels (to avoid borrow issues)
181+
let state = self.state.clone();
182+
162183
// === Transport Bar ===
184+
let cmd_tx2 = cmd_tx.clone();
163185
egui::TopBottomPanel::top("transport_bar")
164186
.min_height(60.0)
165187
.show(ctx, |ui| {
166-
self.transport.show(ui, &self.state, |cmd| self.send_command(cmd));
188+
self.transport.show(ui, &state, |cmd| { let _ = cmd_tx2.send(cmd); });
167189
});
168190

169191
// === Bottom Panel (Mixer) ===
170192
if self.show_mixer {
193+
let cmd_tx2 = cmd_tx.clone();
171194
egui::TopBottomPanel::bottom("mixer_panel")
172195
.resizable(true)
173196
.default_height(200.0)
174197
.min_height(100.0)
175198
.show(ctx, |ui| {
176-
self.mixer.show(ui, &self.state, |cmd| self.send_command(cmd));
199+
self.mixer.show(ui, &state, |cmd| { let _ = cmd_tx2.send(cmd); });
177200
});
178201
}
179202

180203
// === Left Panel (Browser) ===
181204
if self.show_browser {
205+
let cmd_tx2 = cmd_tx.clone();
182206
egui::SidePanel::left("browser_panel")
183207
.resizable(true)
184208
.default_width(200.0)
185209
.min_width(150.0)
186210
.show(ctx, |ui| {
187-
self.browser.show(ui, &self.state, |cmd| self.send_command(cmd));
211+
self.browser.show(ui, &state, |cmd| { let _ = cmd_tx2.send(cmd); });
188212
});
189213
}
190214

191215
// === Right Panel (Inspector) ===
192216
if self.show_inspector {
217+
let cmd_tx2 = cmd_tx.clone();
193218
egui::SidePanel::right("inspector_panel")
194219
.resizable(true)
195220
.default_width(250.0)
196221
.min_width(200.0)
197222
.show(ctx, |ui| {
198-
self.inspector.show(ui, &self.state, |cmd| self.send_command(cmd));
223+
self.inspector.show(ui, &state, |cmd| { let _ = cmd_tx2.send(cmd); });
199224
});
200225
}
201226

202227
// === Central Area (Track List + Timeline) ===
228+
let cmd_tx2 = cmd_tx.clone();
229+
let cmd_tx3 = cmd_tx.clone();
203230
egui::CentralPanel::default().show(ctx, |ui| {
204231
ui.horizontal(|ui| {
205232
// Track list (left side, fixed width)
206-
egui::Frame::none()
233+
egui::Frame::new()
207234
.fill(ui.visuals().extreme_bg_color)
208235
.show(ui, |ui| {
209236
ui.set_width(200.0);
210237
self.track_list.show(
211238
ui,
212-
&self.state,
213-
|cmd| self.send_command(cmd)
239+
&state,
240+
|cmd| { let _ = cmd_tx2.send(cmd); }
214241
);
215242
});
216243

217244
// Timeline (right side, fills remaining space)
218245
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min).with_main_wrap(false), |ui| {
219246
self.timeline.show(
220247
ui,
221-
&self.state,
222-
|cmd| self.send_command(cmd),
248+
&state,
249+
|cmd| { let _ = cmd_tx3.send(cmd); },
223250
&mut self.timeline_zoom,
224251
&mut self.timeline_scroll
225252
);

apps/egui_ui/src/panels/browser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! File browser for audio files and VST plugins
44
5-
use eframe::egui::{self, Color32, RichText, Sense, Vec2};
5+
use eframe::egui::{self, Color32, RichText};
66
use rysyn_ffi_bridge::{Command, StateSnapshot};
77
use std::path::PathBuf;
88

apps/egui_ui/src/panels/inspector.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! Shows properties of selected items (tracks, clips, plugins)
44
5-
use eframe::egui::{self, Color32, RichText, Vec2};
5+
use eframe::egui::{self, Color32, RichText};
66
use rysyn_ffi_bridge::{Command, StateSnapshot, TrackState, ClipState};
77

88
#[derive(Default)]
@@ -243,7 +243,7 @@ impl InspectorPanel {
243243
egui::DragValue::new(&mut length)
244244
.speed(0.1)
245245
.suffix(" beats")
246-
.clamp_range(0.1..=1000.0)
246+
.range(0.1..=1000.0)
247247
).changed() {
248248
send_cmd(Command::ResizeClip {
249249
clip_id: clip.id,
@@ -349,7 +349,7 @@ impl InspectorPanel {
349349
if ui.add(
350350
egui::DragValue::new(&mut bpm)
351351
.speed(0.1)
352-
.clamp_range(20.0..=300.0)
352+
.range(20.0..=300.0)
353353
).changed() {
354354
send_cmd(Command::SetTempo { bpm });
355355
}
@@ -361,10 +361,10 @@ impl InspectorPanel {
361361
ui.horizontal(|ui| {
362362
if ui.add(
363363
egui::DragValue::new(&mut num)
364-
.clamp_range(1..=16)
364+
.range(1..=16)
365365
).changed() || ui.add(
366366
egui::DragValue::new(&mut denom)
367-
.clamp_range(1..=16)
367+
.range(1..=16)
368368
).changed() {
369369
send_cmd(Command::SetTimeSignature {
370370
numerator: num,

apps/egui_ui/src/panels/mixer.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ impl MixerPanel {
4444
) {
4545
let available_height = ui.available_height();
4646

47-
egui::Frame::none()
47+
egui::Frame::new()
4848
.fill(Color32::from_rgb(40, 40, 45))
4949
.stroke(Stroke::new(1.0, Color32::from_rgb(60, 60, 65)))
50-
.rounding(4.0)
50+
.corner_radius(4.0)
5151
.show(ui, |ui| {
5252
ui.allocate_ui(Vec2::new(CHANNEL_WIDTH, available_height), |ui| {
5353
ui.vertical(|ui| {
@@ -215,10 +215,10 @@ impl MixerPanel {
215215
) {
216216
let available_height = ui.available_height();
217217

218-
egui::Frame::none()
218+
egui::Frame::new()
219219
.fill(Color32::from_rgb(50, 45, 45))
220220
.stroke(Stroke::new(1.0, Color32::from_rgb(80, 70, 70)))
221-
.rounding(4.0)
221+
.corner_radius(4.0)
222222
.show(ui, |ui| {
223223
ui.allocate_ui(Vec2::new(CHANNEL_WIDTH + 20.0, available_height), |ui| {
224224
ui.vertical(|ui| {

apps/egui_ui/src/panels/timeline.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ impl TimelinePanel {
282282
painter.rect_stroke(
283283
clip_rect.expand(2.0),
284284
4.0,
285-
Stroke::new(2.0, Color32::WHITE)
285+
Stroke::new(2.0, Color32::WHITE),
286+
egui::StrokeKind::Outside
286287
);
287288
}
288289

@@ -293,7 +294,8 @@ impl TimelinePanel {
293294
painter.rect_stroke(
294295
clip_rect,
295296
4.0,
296-
Stroke::new(1.0, color.linear_multiply(0.7))
297+
Stroke::new(1.0, color.linear_multiply(0.7)),
298+
egui::StrokeKind::Outside
297299
);
298300

299301
// Clip name

apps/egui_ui/src/panels/track_list.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ impl TrackListPanel {
7575
ui.painter().rect_stroke(
7676
rect,
7777
4.0,
78-
Stroke::new(1.0, Color32::from_rgb(60, 60, 65))
78+
Stroke::new(1.0, Color32::from_rgb(60, 60, 65)),
79+
egui::StrokeKind::Outside
7980
);
8081

8182
// Track color indicator

apps/egui_ui/src/panels/transport.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! Play/Pause/Stop, Record, Loop, BPM, Time Display
44
5-
use eframe::egui::{self, Color32, RichText, Sense, Vec2};
5+
use eframe::egui::{self, Color32, RichText, Vec2};
66
use rysyn_ffi_bridge::{Command, StateSnapshot};
77

88
#[derive(Default)]
@@ -91,18 +91,20 @@ impl TransportPanel {
9191

9292
// Editable BPM field
9393
let bpm_text = self.bpm_edit.clone()
94-
.unwrap_or_else(|| format!("{:.1}", state.transport.bpm));
94+
.unwrap_or_else(|| format!("{:.1}", state.transport.tempo));
9595

96+
let mut edit_text = self.bpm_edit.get_or_insert(bpm_text).clone();
9697
let response = ui.add(
97-
egui::TextEdit::singleline(&mut self.bpm_edit.get_or_insert(bpm_text))
98+
egui::TextEdit::singleline(&mut edit_text)
9899
.desired_width(60.0)
99100
.font(egui::TextStyle::Monospace)
100101
);
102+
self.bpm_edit = Some(edit_text);
101103

102104
if response.lost_focus() {
103105
if let Some(ref text) = self.bpm_edit {
104106
if let Ok(new_bpm) = text.parse::<f64>() {
105-
send_cmd(Command::SetBpm { bpm: new_bpm });
107+
send_cmd(Command::SetTempo { bpm: new_bpm });
106108
}
107109
}
108110
self.bpm_edit = None;
@@ -160,7 +162,7 @@ fn transport_button_colored(
160162
).on_hover_text(tooltip)
161163
}
162164

163-
fn format_position(beats: f64, beats_per_bar: u8) -> String {
165+
fn format_position(beats: f64, beats_per_bar: u32) -> String {
164166
let beats_per_bar = beats_per_bar.max(1) as f64;
165167
let bar = (beats / beats_per_bar) as i32 + 1;
166168
let beat_in_bar = (beats % beats_per_bar) + 1.0;

0 commit comments

Comments
 (0)