Custom Tabs
Custom tabs let you add your own panel to the control window. Use them when you need controls that don't fit the standard parameter-slider model: toggle groups, waveform previews, complex layouts, or widgets that act on multiple parameters at once.
Implementing AnyGuiTab
#![allow(unused)] fn main() { use rustjay_engine::prelude::*; struct MyTab; impl AnyGuiTab for MyTab { fn name(&self) -> &str { "My Effect" } fn draw( &mut self, ui: &imgui::Ui, app_state: &mut dyn std::any::Any, engine: &mut EngineState, ) { // Downcast app_state to your concrete type let state = app_state .downcast_mut::<MyState>() .expect("MyTab: wrong state type"); // Draw ImGui widgets ui.slider_config("Intensity", 0.0_f32, 1.0_f32) .build(&mut state.intensity); if ui.button("Reset") { state.intensity = 0.0; } } } }
Register it with run_with_tabs:
fn main() -> anyhow::Result<()> { rustjay_engine::run_with_tabs(MyEffect, vec![Box::new(MyTab)]) }
The tab appears at the right end of the tab bar.
Replacing a built-in tab
If your effect has its own colour controls and you want one custom "Effect" tab instead of the built-in Color tab:
#![allow(unused)] fn main() { impl AnyGuiTab for MyTab { fn name(&self) -> &str { "Effect" } fn replaces(&self) -> Option<BuiltinTab> { Some(BuiltinTab::Color) // hides the built-in Color tab } fn draw(&mut self, ui: &imgui::Ui, app_state: &mut dyn std::any::Any, _engine: &mut EngineState) { let state = app_state.downcast_mut::<MyState>().unwrap(); ui.slider_config("Hue Shift", -180.0_f32, 180.0_f32).build(&mut state.hue_shift); ui.slider_config("Saturation", 0.0_f32, 2.0_f32).build(&mut state.saturation); } } }
ImGui widget reference
A quick reference of frequently used ImGui widgets:
#![allow(unused)] fn main() { // Sliders ui.slider_config("Label", min, max).build(&mut state.value); // Drag (finer control, no visible range) imgui::Drag::new("Label").speed(0.01).build(ui, &mut state.value); // Checkbox ui.checkbox("Enabled", &mut state.enabled); // Combo (dropdown) let items = ["Replace", "Add", "Multiply", "Screen"]; let mut current = state.blend_mode as usize; if ui.combo_simple_string("Blend Mode", &mut current, &items) { state.blend_mode = current as u32; } // Button if ui.button("Randomise") { /* ... */ } // Colour picker (returns [f32; 4]) ui.color_edit4_config("Tint", imgui::ColorEditFlags::NO_ALPHA) .build(&mut state.tint); // Text ui.text(format!("BPM: {:.1}", engine.effective_bpm())); // Separator + header ui.separator(); ui.text_colored([1.0, 0.8, 0.2, 1.0], "-- Section --"); }
Using the egui backend
If you're building with the egui feature, use EguiAnyTab instead and draw with egui::Ui. The pattern is identical — implement the trait, downcast app_state, draw widgets — but the widget API differs.
// Cargo.toml: rustjay-engine = { ..., features = ["egui"] } use rustjay_engine::prelude::*; struct MyEguiTab; impl EguiAnyTab for MyEguiTab { fn name(&self) -> &str { "Effect" } fn draw( &mut self, ui: &egui::Ui, app_state: &mut dyn std::any::Any, engine: &mut EngineState, ) { let state = app_state.downcast_mut::<MyState>().unwrap(); ui.add(egui::Slider::new(&mut state.intensity, 0.0..=1.0).text("Intensity")); } } fn main() -> anyhow::Result<()> { rustjay_engine::run_with_tabs(MyEffect, vec![Box::new(MyEguiTab)]) }
See examples/delta-egui for a complete egui-backend example.