| use avm_stats::{Plane, PlaneType, Spatial}; |
| use egui::{vec2, Button, Checkbox, RichText, Ui}; |
| |
| use crate::app_state::AppState; |
| use crate::settings::{DistortionView, FrameSortOrder, MotionFieldColoring}; |
| use crate::stream::{ChangeFrame, CurrentFrame}; |
| use crate::views::RenderView; |
| |
| use super::ViewMode; |
| |
| const PLAYBACK_BUTTON_HEIGHT: f32 = 30.0; |
| const PLAYBACK_BUTTON_WIDTH: f32 = 30.0; |
| const PLAYBACK_BUTTON_FONT_SIZE: f32 = 18.0; |
| |
| fn create_playback_button(icon: &str) -> Button { |
| Button::new(RichText::new(format!(" {icon} ")).size(PLAYBACK_BUTTON_FONT_SIZE)) |
| .min_size(vec2(PLAYBACK_BUTTON_WIDTH, PLAYBACK_BUTTON_HEIGHT)) |
| } |
| |
| pub struct ControlsViewer; |
| |
| impl RenderView for ControlsViewer { |
| fn title(&self) -> String { |
| "Controls".into() |
| } |
| |
| fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> { |
| ui.horizontal(|ui| { |
| ui.vertical(|ui| { |
| ui.label("View"); |
| let frame_sort_options = [FrameSortOrder::Decode, FrameSortOrder::Display]; |
| egui::ComboBox::from_label("Frame sort order") |
| .selected_text(format!("{:?}", state.settings.persistent.frame_sort_order)) |
| .show_ui(ui, |ui| { |
| for &sort_option in frame_sort_options.iter() { |
| ui.selectable_value( |
| &mut state.settings.persistent.frame_sort_order, |
| sort_option, |
| format!("{sort_option:?}"), |
| ); |
| } |
| }); |
| ui.end_row(); |
| |
| let plane_options = [ |
| PlaneType::Planar(Plane::Y), |
| PlaneType::Planar(Plane::U), |
| PlaneType::Planar(Plane::V), |
| PlaneType::Rgb, |
| ]; |
| |
| egui::ComboBox::from_label("Plane view") |
| .selected_text(format!("{}", state.settings.sharable.current_plane)) |
| .show_ui(ui, |ui| { |
| for &plane_option in plane_options.iter() { |
| ui.selectable_value( |
| &mut state.settings.sharable.current_plane, |
| plane_option, |
| format!("{plane_option}"), |
| ); |
| } |
| }); |
| ui.end_row(); |
| |
| ui.add_enabled( |
| state.settings.sharable.view_mode.view_settings().pixel_type.is_delta(), |
| egui::Checkbox::new(&mut state.settings.sharable.show_relative_delta, "Show relative delta"), |
| ); |
| |
| ui.end_row(); |
| |
| ui.checkbox(&mut state.settings.sharable.show_overlay, "Show overlay"); |
| |
| ui.end_row(); |
| |
| ui.checkbox(&mut state.settings.sharable.show_yuv, "Show YUV"); |
| |
| ui.end_row(); |
| |
| if ui.button("Reset Zoom").clicked() { |
| if let Some(frame) = state.stream.current_frame() { |
| state.settings.sharable.world_bounds = frame.rect(); |
| } |
| } |
| }); |
| if let Some(stream) = &mut state.stream { |
| ui.separator(); |
| ui.vertical(|ui| { |
| ui.label("Playback"); |
| ui.horizontal(|ui| { |
| let frame_sort_order: FrameSortOrder = state.settings.persistent.frame_sort_order; |
| if ui |
| .add(create_playback_button("⏮")) |
| .on_hover_text("Go to first frame") |
| .clicked() |
| { |
| stream.change_frame(ChangeFrame::first().order(frame_sort_order)); |
| } |
| if ui |
| .add(create_playback_button("⏪")) |
| .on_hover_text("Go to previous frame") |
| .clicked() |
| { |
| stream.change_frame(ChangeFrame::prev().order(frame_sort_order)); |
| } |
| let play_pause = if state.settings.playback.playback_running { |
| "⏸" |
| } else { |
| "▶" |
| }; |
| if ui |
| .add(create_playback_button(play_pause)) |
| .on_hover_text("Start/stop playback") |
| .clicked() |
| { |
| state.settings.playback.playback_running = !state.settings.playback.playback_running; |
| } |
| |
| if ui |
| .add(create_playback_button("⏩")) |
| .on_hover_text("Go to next frame") |
| .clicked() |
| { |
| stream.change_frame(ChangeFrame::next().order(frame_sort_order)); |
| } |
| if ui |
| .add(create_playback_button("⏭")) |
| .on_hover_text("Go to last frame") |
| .clicked() |
| { |
| stream.change_frame(ChangeFrame::last().order(frame_sort_order)); |
| } |
| }); |
| // TODO(comc): Re-enable after adding pause logic for pending loads. |
| // ui.checkbox( |
| // &mut state.settings.playback.show_loaded_frames_only, |
| // "Playback loaded frames only", |
| // ); |
| ui.checkbox(&mut state.settings.playback.playback_loop, "Loop playback"); |
| ui.label("Playback FPS:"); |
| ui.add(egui::Slider::new(&mut state.settings.playback.playback_fps, 1.0..=60.0).step_by(1.0)); |
| }); |
| } |
| |
| if state.settings.sharable.view_mode == ViewMode::Heatmap { |
| ui.separator(); |
| ui.vertical(|ui| { |
| ui.label("Heatmap"); |
| ui.checkbox(&mut state.settings.sharable.show_heatmap_legend, "Show Heatmap Legend"); |
| ui.end_row(); |
| ui.label("Histogram buckets"); |
| ui.add(egui::Slider::new( |
| &mut state.settings.sharable.heatmap_settings.histogram_buckets, |
| 4..=100, |
| )); |
| ui.end_row(); |
| ui.checkbox( |
| &mut state.settings.sharable.heatmap_histogram_log_scale, |
| "Histogram log scale", |
| ); |
| ui.end_row(); |
| ui.label("Symbol filter"); |
| ui.text_edit_singleline(&mut state.settings.sharable.heatmap_settings.symbol_filter); |
| if ui.button("Recalculate heatmap").clicked() { |
| if let Some(stream) = &state.stream { |
| stream.images.clear_heatmaps(); |
| } |
| } |
| }); |
| } |
| |
| if let ViewMode::Distortion(mut distortion_view) = state.settings.sharable.view_mode { |
| ui.separator(); |
| ui.vertical(|ui| { |
| let distortion_view_options = [ |
| DistortionView::Distortion, |
| DistortionView::Original, |
| DistortionView::Reconstruction, |
| ]; |
| egui::ComboBox::from_label("Displayed pixels") |
| .selected_text(distortion_view.name()) |
| .show_ui(ui, |ui| { |
| for &distortion_view_option in distortion_view_options.iter() { |
| ui.selectable_value( |
| &mut distortion_view, |
| distortion_view_option, |
| distortion_view_option.name(), |
| ); |
| } |
| }); |
| ui.end_row(); |
| }); |
| state.settings.sharable.view_mode = ViewMode::Distortion(distortion_view); |
| } |
| |
| if state.settings.sharable.view_mode == ViewMode::Motion { |
| ui.separator(); |
| ui.vertical(|ui| { |
| ui.label("Motion"); |
| ui.checkbox(&mut state.settings.sharable.motion_field.show, "Show motion field"); |
| ui.end_row(); |
| ui.checkbox( |
| &mut state.settings.sharable.motion_field.show_origin, |
| "Show motion vector origin", |
| ); |
| ui.end_row(); |
| ui.add_enabled( |
| state.settings.sharable.motion_field.show, |
| Checkbox::new( |
| &mut state.settings.sharable.motion_field.normalize, |
| "Normalize vector lengths", |
| ), |
| ); |
| ui.end_row(); |
| ui.label("Motion vector scale:"); |
| ui.add(egui::Slider::new(&mut state.settings.sharable.motion_field.scale, 0.1..=10.0).step_by(0.1)); |
| |
| let coloring_options = [ |
| MotionFieldColoring::RefFrames, |
| MotionFieldColoring::PastFuture, |
| MotionFieldColoring::Monochrome, |
| ]; |
| egui::ComboBox::from_label("Coloring scheme") |
| .selected_text(state.settings.sharable.motion_field.coloring.name()) |
| .show_ui(ui, |ui| { |
| for &coloring_option in coloring_options.iter() { |
| ui.selectable_value( |
| &mut state.settings.sharable.motion_field.coloring, |
| coloring_option, |
| coloring_option.name(), |
| ); |
| } |
| }); |
| ui.end_row(); |
| |
| ui.label("Granularity (in 4x4 units):"); |
| ui.checkbox(&mut state.settings.sharable.motion_field.auto_granularity, "Automatic"); |
| ui.add_enabled( |
| !state.settings.sharable.motion_field.auto_granularity, |
| egui::Slider::new(&mut state.settings.sharable.motion_field.granularity, 1..=32), |
| ); |
| if let Some(frame) = state.stream.current_frame() { |
| if state.settings.sharable.motion_field.auto_granularity { |
| state.settings.sharable.motion_field.granularity = frame.height() as usize / 64; |
| } |
| } |
| ui.end_row(); |
| }); |
| } |
| }); |
| Ok(()) |
| } |
| } |