diff --git a/src/app.rs b/src/app.rs index f2bc659..beef014 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,9 @@ -use crate::config::Config; use crate::diff_image_loader::DiffImageLoader; use crate::settings::Settings; use crate::state::{AppState, AppStateRef, PageRef, SystemCommand, ViewerSystemCommand}; use crate::{DiffSource, bar, home, viewer}; -use eframe::egui::{Context, Key, Modifiers}; +use crate::{config::Config, state::View}; +use eframe::egui::{Context, Modifiers}; use eframe::{Frame, Storage, egui}; use egui_extras::install_image_loaders; use egui_inbox::UiInbox; @@ -172,21 +172,15 @@ impl App { state.send(ViewerSystemCommand::SelectSnapshot(new_index)); } - let handle_key = |key: Key, toggle: &mut bool| { - if ctx.input_mut(|i| i.key_pressed(key)) { - *toggle = true; + let mut new_view = vs.state.view; + for view in View::ALL { + if ctx.input_mut(|i| i.consume_key(Default::default(), view.key())) { + new_view = view; } - if ctx.input_mut(|i| i.key_released(key)) { - *toggle = false; - } - }; - - let mut view_filter = vs.state.view_filter; - handle_key(Key::Num1, &mut view_filter.show_old); - handle_key(Key::Num2, &mut view_filter.show_new); - handle_key(Key::Num3, &mut view_filter.show_diff); - if view_filter != vs.state.view_filter { - state.send(ViewerSystemCommand::SetViewFilter(view_filter)); + } + + if new_view != vs.state.view { + state.send(ViewerSystemCommand::SetView(new_view)); } } } diff --git a/src/cli.rs b/src/cli.rs index e0a0e56..9e5b5a8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,7 +18,7 @@ pub enum Commands { Files { directory: Option }, /// Compare images between current branch and default branch Git { repo_path: Option }, - /// Compare images between PR branches from GitHub PR URL (needs to be run from within the repo) + /// Compare images between PR branches from GitHub PR URL Pr { url: String }, /// Load and compare snapshot files from a zip archive (URL or local file) Archive { source: String }, diff --git a/src/snapshot.rs b/src/snapshot.rs index 9fd4b55..8233130 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -1,6 +1,6 @@ -use crate::diff_image_loader; use crate::diff_image_loader::DiffOptions; use crate::state::{AppStateRef, PageRef}; +use crate::{diff_image_loader, state::View}; use eframe::egui; use eframe::egui::{Color32, ImageSource}; use std::path::PathBuf; @@ -88,14 +88,14 @@ impl Snapshot { state: &AppStateRef<'a>, uri: String, opacity: f32, - show_all: bool, + blend_all: bool, ) -> eframe::egui::Image<'a> { let mut image = eframe::egui::Image::new(uri) .texture_options(eframe::egui::TextureOptions { magnification: state.settings.texture_magnification, ..eframe::egui::TextureOptions::default() }) - .tint(Color32::from_white_alpha(if show_all { + .tint(Color32::from_white_alpha(if blend_all { (255.0 * opacity) as u8 } else { u8::MAX @@ -114,37 +114,37 @@ impl Snapshot { let PageRef::DiffViewer(vs) = &state.page else { return None; }; - let show_all = vs.view_filter.all(); - let show_old = vs.view_filter.show_old; - (show_all || show_old) + let blend_all = vs.view == View::BlendAll; + let show_old = vs.view == View::Old; + (blend_all || show_old) .then(|| self.old_uri()) .flatten() - .map(|uri| Self::make_image(state, uri, 1.0, show_all)) + .map(|uri| Self::make_image(state, uri, 1.0, blend_all)) } pub fn new_image<'a>(&self, state: &AppStateRef<'a>) -> Option> { let PageRef::DiffViewer(vs) = &state.page else { return None; }; - let show_all = vs.view_filter.all(); - let show_new = vs.view_filter.show_new; - (show_all || show_new) + let blend_all = vs.view == View::BlendAll; + let show_new = vs.view == View::New; + (blend_all || show_new) .then(|| self.new_uri()) .flatten() - .map(|new_uri| Self::make_image(state, new_uri, state.settings.new_opacity, show_all)) + .map(|new_uri| Self::make_image(state, new_uri, state.settings.new_opacity, blend_all)) } pub fn diff_image<'a>(&self, state: &AppStateRef<'a>) -> Option> { let PageRef::DiffViewer(vs) = &state.page else { return None; }; - let show_all = vs.view_filter.all(); - let show_diff = vs.view_filter.show_diff; - (show_all || show_diff) + let blend_all = vs.view == View::BlendAll; + let show_diff = vs.view == View::Diff; + (blend_all || show_diff) .then(|| self.diff_uri(state.settings.use_original_diff, state.settings.options)) .flatten() .map(|diff_uri| { - Self::make_image(state, diff_uri, state.settings.diff_opacity, show_all) + Self::make_image(state, diff_uri, state.settings.diff_opacity, blend_all) }) } } diff --git a/src/state.rs b/src/state.rs index 26810d3..804fd9e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,7 +6,7 @@ use crate::github::pr::GithubPr; use crate::loaders::SnapshotLoader; use crate::settings::Settings; use crate::snapshot::Snapshot; -use eframe::egui::Context; +use eframe::egui::{self, Context}; use egui_inbox::UiInboxSender; use octocrab::Octocrab; use std::ops::Deref; @@ -31,7 +31,7 @@ pub struct ViewerState { /// If true, this item will scroll into view. pub index_just_selected: bool, pub filter: String, - pub view_filter: ViewFilter, + pub view: View, } impl ViewerState { @@ -52,19 +52,43 @@ impl ViewerState { } } -/// If any is true, only show those, but at full opacity -/// -/// If all are false, show all at their set opacities -#[derive(Default, Copy, Clone, PartialEq, Eq)] -pub struct ViewFilter { - pub show_old: bool, - pub show_new: bool, - pub show_diff: bool, +#[derive(Copy, Clone, Default, PartialEq, Eq)] +pub enum View { + /// View all stacked on each other, with opacity settings. + #[default] + BlendAll, + + /// View old image + Old, + + /// View new image + New, + + /// View diff + Diff, } -impl ViewFilter { - pub fn all(&self) -> bool { - !self.show_old && !self.show_new && !self.show_diff +impl std::fmt::Display for View { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BlendAll => write!(f, "Blend all"), + Self::Old => write!(f, "Old"), + Self::New => write!(f, "New"), + Self::Diff => write!(f, "Diff"), + } + } +} + +impl View { + pub const ALL: [Self; 4] = [Self::BlendAll, Self::Old, Self::New, Self::Diff]; + + pub fn key(self) -> egui::Key { + match self { + Self::BlendAll => egui::Key::Num1, + Self::Old => egui::Key::Num2, + Self::New => egui::Key::Num3, + Self::Diff => egui::Key::Num4, + } } } @@ -198,7 +222,7 @@ pub enum SystemCommand { pub enum ViewerSystemCommand { SetFilter(String), SelectSnapshot(usize), - SetViewFilter(ViewFilter), + SetView(View), } impl From for SystemCommand { @@ -217,7 +241,7 @@ impl AppState { index: 0, index_just_selected: true, loader, - view_filter: ViewFilter::default(), + view: View::default(), }); } SystemCommand::GithubAuth(auth) => { @@ -270,8 +294,8 @@ impl ViewerState { self.index_just_selected = true; } } - ViewerSystemCommand::SetViewFilter(view_filter) => { - self.view_filter = view_filter; + ViewerSystemCommand::SetView(view_filter) => { + self.view = view_filter; } } } diff --git a/src/viewer/viewer_options.rs b/src/viewer/viewer_options.rs index 1141204..6d617b8 100644 --- a/src/viewer/viewer_options.rs +++ b/src/viewer/viewer_options.rs @@ -1,28 +1,37 @@ -use crate::settings::ImageMode; use crate::state::{SystemCommand, ViewerAppStateRef, ViewerSystemCommand}; -use eframe::egui::{Checkbox, Slider, TextureFilter, Ui}; +use crate::{settings::ImageMode, state::View}; +use eframe::egui::{self, Slider, TextureFilter, Ui}; pub fn viewer_options(ui: &mut Ui, state: &ViewerAppStateRef<'_>) { let mut settings = state.app.settings.clone(); ui.group(|ui| { - ui.strong("View only"); - let mut view_filter = state.view_filter; - ui.add_enabled( - false, - Checkbox::new(&mut state.view_filter.all(), "All with opacity"), - ); - ui.checkbox(&mut view_filter.show_old, "Old (1)"); - ui.checkbox(&mut view_filter.show_new, "New (2)"); - ui.checkbox(&mut view_filter.show_diff, "Diff (3)"); - if view_filter != state.view_filter { - state - .app - .send(ViewerSystemCommand::SetViewFilter(view_filter)); + ui.strong("View"); + let mut new_view = state.view; + + for view in View::ALL { + ui.radio_value( + &mut new_view, + view, + format!("{view} ({})", view.key().name()), + ); + } + + ui.label("Toggle old/new with SPACE"); + ui.input(|i| { + if i.key_pressed(egui::Key::Space) { + new_view = View::New; + } else if i.key_released(egui::Key::Space) { + new_view = View::Old; + } + }); + + if new_view != state.view { + state.app.send(ViewerSystemCommand::SetView(new_view)); } }); - ui.add_enabled_ui(state.view_filter.all(), |ui| { + ui.add_enabled_ui(state.view == View::BlendAll, |ui| { ui.add(Slider::new(&mut settings.new_opacity, 0.0..=1.0).text("New Opacity")); ui.add(Slider::new(&mut settings.diff_opacity, 0.0..=1.0).text("Diff Opacity")); });