use kiss3d::conrod::{self, Borderable, Colorable, Labelable, Positionable, Sizeable, Widget};
use kiss3d::window::Window;
use crate::testbed::{RunMode, TestbedActionFlags, TestbedState, TestbedStateFlags};
use na::RealField;
use nphysics::world::DefaultMechanicalWorld;
const SIDEBAR_W: f64 = 200.0;
const ELEMENT_W: f64 = SIDEBAR_W - 20.0;
const ELEMENT_H: f64 = 20.0;
const VSPACE: f64 = 4.0;
const TITLE_VSPACE: f64 = 4.0;
const LEFT_MARGIN: f64 = 10.0;
const ALPHA: f32 = 0.9;
widget_ids! {
pub struct ConrodIds {
canvas,
title_backends_list,
title_demos_list,
title_slider_vel_iter,
title_slider_pos_iter,
title_slider_ccd_substeps,
title_warmstart_coeff,
title_frequency,
backends_list,
demos_list,
button_pause,
button_single_step,
button_restart,
button_quit,
button_prev_example,
button_next_example,
slider_vel_iter,
slider_pos_iter,
slider_ccd_substeps,
slider_warmstart_coeff,
slider_frequency,
toggle_sleep,
toggle_warm_starting,
toggle_sub_stepping,
toggle_shapes,
toggle_joints,
toggle_aabbs,
toggle_contact_points,
toggle_contact_normals,
toggle_center_of_masses,
toggle_statistics,
toggle_profile,
toggle_wireframe,
separator0,
separator1,
separator2,
}
}
pub struct TestbedUi {
ids: ConrodIds,
}
impl TestbedUi {
pub fn new(window: &mut Window) -> Self {
use conrod::position::{Align, Direction, Padding, Position, Relative};
let mut ui = window.conrod_ui_mut();
ui.theme = conrod::Theme {
name: "Testbed theme".to_string(),
padding: Padding::none(),
x_position: Position::Relative(Relative::Align(Align::Start), None),
y_position: Position::Relative(Relative::Direction(Direction::Backwards, 20.0), None),
background_color: conrod::color::DARK_CHARCOAL.alpha(ALPHA),
shape_color: conrod::color::LIGHT_CHARCOAL.alpha(ALPHA),
border_color: conrod::color::BLACK.alpha(ALPHA),
border_width: 0.0,
label_color: conrod::color::WHITE.alpha(ALPHA),
font_id: None,
font_size_large: 15,
font_size_medium: 11,
font_size_small: 8,
widget_styling: conrod::theme::StyleMap::default(),
mouse_drag_threshold: 0.0,
double_click_threshold: std::time::Duration::from_millis(500),
};
Self {
ids: ConrodIds::new(ui.widget_id_generator()),
}
}
pub fn update<N: RealField>(
&mut self,
window: &mut Window,
world: &mut DefaultMechanicalWorld<N>,
state: &mut TestbedState<N>,
) {
let ui_root = window.conrod_ui().window;
let mut ui = window.conrod_ui_mut().set_widgets();
conrod::widget::Canvas::new()
.scroll_kids_vertically()
.mid_right_with_margin(10.0)
.w(SIDEBAR_W)
.padded_h_of(ui_root, 10.0)
.set(self.ids.canvas, &mut ui);
if state.backend_names.len() > 1 && !state.example_names.is_empty() {
conrod::widget::Text::new("Select backend:")
.top_left_with_margins_on(self.ids.canvas, VSPACE, LEFT_MARGIN)
.set(self.ids.title_backends_list, &mut ui);
for selected in conrod::widget::DropDownList::new(
&state.backend_names,
Some(state.selected_backend),
)
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_backends_list, TITLE_VSPACE)
.left_justify_label()
.w_h(ELEMENT_W, ELEMENT_H)
.color(conrod::color::LIGHT_CHARCOAL) .set(self.ids.backends_list, &mut ui)
{
if selected != state.selected_backend {
state.selected_backend = selected;
state
.action_flags
.set(TestbedActionFlags::BACKEND_CHANGED, true)
}
}
separator(
self.ids.canvas,
self.ids.backends_list,
self.ids.separator0,
&mut ui,
);
} else {
conrod::widget::Text::new("")
.top_left_with_margins_on(self.ids.canvas, 0.0, LEFT_MARGIN)
.set(self.ids.separator0, &mut ui);
}
let display_ticks = state.example_names.len() > 1;
let _select_example_title = if display_ticks {
"Select example:"
} else {
"Current example:"
};
let tick_width = if display_ticks { 20.0 } else { 0.0 };
conrod::widget::Text::new("Select example:")
.down_from(self.ids.separator0, VSPACE)
.set(self.ids.title_demos_list, &mut ui);
for selected in
conrod::widget::DropDownList::new(&state.example_names, Some(state.selected_example))
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_demos_list, TITLE_VSPACE)
.left_justify_label()
.w_h(ELEMENT_W - tick_width, ELEMENT_H)
.color(conrod::color::LIGHT_CHARCOAL) .set(self.ids.demos_list, &mut ui)
{
if selected != state.selected_example {
state.selected_example = selected;
state
.action_flags
.set(TestbedActionFlags::EXAMPLE_CHANGED, true)
}
}
if display_ticks {
for _click in conrod::widget::Button::new()
.label("<")
.align_middle_x_of(self.ids.canvas)
.left_from(self.ids.demos_list, 0.0)
.w_h(10.0, ELEMENT_H)
.enabled(state.selected_example > 0)
.color(conrod::color::LIGHT_CHARCOAL) .set(self.ids.button_prev_example, &mut ui)
{
if state.selected_example > 0 {
state.selected_example -= 1;
state
.action_flags
.set(TestbedActionFlags::EXAMPLE_CHANGED, true)
}
}
for _click in conrod::widget::Button::new()
.label(">")
.align_middle_x_of(self.ids.canvas)
.right_from(self.ids.demos_list, 0.0)
.w_h(10.0, ELEMENT_H)
.enabled(state.selected_example + 1 < state.example_names.len())
.color(conrod::color::LIGHT_CHARCOAL) .set(self.ids.button_next_example, &mut ui)
{
if state.selected_example + 1 < state.example_names.len() {
state.selected_example += 1;
state
.action_flags
.set(TestbedActionFlags::EXAMPLE_CHANGED, true)
}
}
}
separator(
self.ids.canvas,
self.ids.demos_list,
self.ids.separator1,
&mut ui,
);
let curr_vel_iters = world.integration_parameters.max_velocity_iterations;
let curr_pos_iters = world.integration_parameters.max_position_iterations;
let curr_max_ccd_substeps = world.integration_parameters.max_ccd_substeps;
let curr_warmstart_coeff = world.integration_parameters.warmstart_coeff;
let curr_frequency =
na::convert_unchecked::<_, f64>(world.integration_parameters.inv_dt().round()) as usize;
conrod::widget::Text::new("Vel. Iters.:")
.down_from(self.ids.separator1, VSPACE)
.set(self.ids.title_slider_vel_iter, &mut ui);
for val in conrod::widget::Slider::new(curr_vel_iters as f32, 0.0, 50.0)
.label(&curr_vel_iters.to_string())
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_slider_vel_iter, TITLE_VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.slider_vel_iter, &mut ui)
{
world.integration_parameters.max_velocity_iterations = val as usize;
}
conrod::widget::Text::new("Pos. Iters.:")
.down_from(self.ids.slider_vel_iter, VSPACE)
.set(self.ids.title_slider_pos_iter, &mut ui);
for val in conrod::widget::Slider::new(curr_pos_iters as f32, 0.0, 50.0)
.label(&curr_pos_iters.to_string())
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_slider_pos_iter, TITLE_VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.slider_pos_iter, &mut ui)
{
world.integration_parameters.max_position_iterations = val as usize;
}
conrod::widget::Text::new("CCD substeps:")
.down_from(self.ids.slider_pos_iter, VSPACE)
.set(self.ids.title_slider_ccd_substeps, &mut ui);
for val in conrod::widget::Slider::new(curr_max_ccd_substeps as f32, 0.0, 10.0)
.label(&curr_max_ccd_substeps.to_string())
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_slider_ccd_substeps, TITLE_VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.slider_ccd_substeps, &mut ui)
{
world.integration_parameters.max_ccd_substeps = val as usize;
}
conrod::widget::Text::new("Warm-start coeff.:")
.down_from(self.ids.slider_ccd_substeps, VSPACE)
.set(self.ids.title_warmstart_coeff, &mut ui);
for val in
conrod::widget::Slider::new(na::convert_unchecked(curr_warmstart_coeff), 0.0, 1.0)
.label(&format!("{:.2}", curr_warmstart_coeff))
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_warmstart_coeff, TITLE_VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.slider_warmstart_coeff, &mut ui)
{
world.integration_parameters.warmstart_coeff = na::convert(val);
}
conrod::widget::Text::new("Frequency:")
.down_from(self.ids.slider_warmstart_coeff, VSPACE)
.set(self.ids.title_frequency, &mut ui);
for val in conrod::widget::Slider::new(curr_frequency as f32, 0.0, 240.0)
.label(&format!("{:.2}Hz", curr_frequency))
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.title_frequency, TITLE_VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.slider_frequency, &mut ui)
{
world
.integration_parameters
.set_inv_dt(na::convert(val.round() as f64));
}
let toggle_list = [
("Sleep", self.ids.toggle_sleep, TestbedStateFlags::SLEEP),
(
"Sub-Stepping",
self.ids.toggle_sub_stepping,
TestbedStateFlags::SUB_STEPPING,
),
("", self.ids.separator2, TestbedStateFlags::NONE),
("AABBs", self.ids.toggle_aabbs, TestbedStateFlags::AABBS),
(
"Contacts",
self.ids.toggle_contact_points,
TestbedStateFlags::CONTACT_POINTS,
),
(
"Wireframe",
self.ids.toggle_wireframe,
TestbedStateFlags::WIREFRAME,
),
(
"Profile",
self.ids.toggle_profile,
TestbedStateFlags::PROFILE,
),
];
toggles(
&toggle_list,
self.ids.canvas,
self.ids.slider_frequency,
&mut ui,
&mut state.flags,
);
let label = if state.running == RunMode::Stop {
"Start (T)"
} else {
"Pause (T)"
};
for _press in conrod::widget::Button::new()
.label(label)
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.toggle_profile, VSPACE)
.w_h(ELEMENT_W, ELEMENT_H)
.set(self.ids.button_pause, &mut ui)
{
if state.running == RunMode::Stop {
state.running = RunMode::Running
} else {
state.running = RunMode::Stop
}
}
for _press in conrod::widget::Button::new()
.label("Single Step (S)")
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.button_pause, VSPACE)
.set(self.ids.button_single_step, &mut ui)
{
state.running = RunMode::Step
}
let before_quit_button_id = if !state.example_names.is_empty() {
for _press in conrod::widget::Button::new()
.label("Restart (R)")
.align_middle_x_of(self.ids.canvas)
.down_from(self.ids.button_single_step, VSPACE)
.set(self.ids.button_restart, &mut ui)
{
state.action_flags.set(TestbedActionFlags::RESTART, true);
}
self.ids.button_restart
} else {
self.ids.button_single_step
};
#[cfg(not(target_arch = "wasm32"))]
for _press in conrod::widget::Button::new()
.label("Quit (Esc)")
.align_middle_x_of(self.ids.canvas)
.down_from(before_quit_button_id, VSPACE)
.set(self.ids.button_quit, &mut ui)
{
state.running = RunMode::Quit
}
}
}
fn toggles(
toggles: &[(&str, conrod::widget::Id, TestbedStateFlags)],
canvas: conrod::widget::Id,
prev: conrod::widget::Id,
ui: &mut conrod::UiCell,
flags: &mut TestbedStateFlags,
) {
toggle(
toggles[0].0,
toggles[0].2,
canvas,
prev,
toggles[0].1,
ui,
flags,
);
for win in toggles.windows(2) {
toggle(win[1].0, win[1].2, canvas, win[0].1, win[1].1, ui, flags)
}
}
fn toggle(
title: &str,
flag: TestbedStateFlags,
canvas: conrod::widget::Id,
prev: conrod::widget::Id,
curr: conrod::widget::Id,
ui: &mut conrod::UiCell,
flags: &mut TestbedStateFlags,
) {
if title == "" {
separator(canvas, prev, curr, ui)
} else {
for _pressed in conrod::widget::Toggle::new(flags.contains(flag))
.mid_left_with_margin_on(canvas, LEFT_MARGIN)
.down_from(prev, VSPACE)
.w_h(20.0 , ELEMENT_H)
.label(title)
.label_color(kiss3d::conrod::color::WHITE)
.label_x(conrod::position::Relative::Direction(
conrod::position::Direction::Forwards,
5.0,
))
.border(2.0)
.set(curr, ui)
{
flags.toggle(flag)
}
}
}
fn separator(
canvas: conrod::widget::Id,
prev: conrod::widget::Id,
curr: conrod::widget::Id,
ui: &mut conrod::UiCell,
) {
conrod::widget::Line::centred([-ELEMENT_W / 2.0, 0.0], [ELEMENT_W / 2.0, 0.0])
.align_middle_x_of(canvas)
.down_from(prev, VSPACE)
.w(ELEMENT_W)
.set(curr, ui);
}