From 10a16d9097d96fd6b84f043c99c316d52d0b4e8b Mon Sep 17 00:00:00 2001 From: longmathemagician Date: Thu, 29 Sep 2022 10:02:51 -0700 Subject: [PATCH] Implement simple centered zoom, add second command to recenter button --- dependencies/druid | 2 +- resources/buttons/realsize/active.svg | 184 +++++++++++++++++++++++ resources/buttons/realsize/button.svg | 179 ++++++++++++++++++++++ resources/buttons/realsize/disabled.svg | 178 ++++++++++++++++++++++ resources/buttons/realsize/hot.svg | 184 +++++++++++++++++++++++ resources/buttons/recenter/active.svg | 32 ++-- resources/buttons/recenter/button.svg | 41 ++--- resources/buttons/recenter/disabled.svg | 46 +++--- resources/buttons/recenter/hot.svg | 30 ++-- src/app_state.rs | 32 ++-- src/button_widget.rs | 115 ++++++++++---- src/commands.rs | 19 ++- src/container_widget.rs | 117 ++++++++++----- src/image_widget.rs | 190 ++++++++++++++---------- src/toolbar_widget.rs | 123 ++++++++++----- src/types.rs | 97 ++++++++++-- 16 files changed, 1283 insertions(+), 286 deletions(-) create mode 100644 resources/buttons/realsize/active.svg create mode 100644 resources/buttons/realsize/button.svg create mode 100644 resources/buttons/realsize/disabled.svg create mode 100644 resources/buttons/realsize/hot.svg diff --git a/dependencies/druid b/dependencies/druid index 4a4a47a..40635d0 160000 --- a/dependencies/druid +++ b/dependencies/druid @@ -1 +1 @@ -Subproject commit 4a4a47a0a985d11ca23cd802610ff0db36a7ad74 +Subproject commit 40635d0b6359b7b2645eec0a112b7d6191b96184 diff --git a/resources/buttons/realsize/active.svg b/resources/buttons/realsize/active.svg new file mode 100644 index 0000000..0433882 --- /dev/null +++ b/resources/buttons/realsize/active.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/buttons/realsize/button.svg b/resources/buttons/realsize/button.svg new file mode 100644 index 0000000..74401ab --- /dev/null +++ b/resources/buttons/realsize/button.svg @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/buttons/realsize/disabled.svg b/resources/buttons/realsize/disabled.svg new file mode 100644 index 0000000..209b6bb --- /dev/null +++ b/resources/buttons/realsize/disabled.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/buttons/realsize/hot.svg b/resources/buttons/realsize/hot.svg new file mode 100644 index 0000000..d9df111 --- /dev/null +++ b/resources/buttons/realsize/hot.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/buttons/recenter/active.svg b/resources/buttons/recenter/active.svg index 0433882..2e43ef4 100644 --- a/resources/buttons/recenter/active.svg +++ b/resources/buttons/recenter/active.svg @@ -7,8 +7,8 @@ viewBox="0 0 8.4666664 8.466667" version="1.1" id="svg5" - inkscape:version="1.2 (dc2aedaf03, 2022-05-15)" - sodipodi:docname="recenter_active.svg" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + sodipodi:docname="active.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -26,14 +26,14 @@ units="px" width="64px" inkscape:zoom="22.627417" - inkscape:cx="19.136077" + inkscape:cx="16.219262" inkscape:cy="15.291184" inkscape:window-width="1920" - inkscape:window-height="995" - inkscape:window-x="0" - inkscape:window-y="0" + inkscape:window-height="1009" + inkscape:window-x="-8" + inkscape:window-y="-8" inkscape:window-maximized="1" - inkscape:current-layer="g1219" + inkscape:current-layer="path961-4" inkscape:showpageshadow="0" inkscape:deskcolor="#d1d1d1"> - + style="fill:#5e81ac"> + transform="matrix(0,-0.65745479,0.3906802,0,19.78396,12.409401)" + style="fill:#5e81ac"> - + transform="matrix(0,-0.65745479,-0.3906802,0,22.251964,12.558589)" + style="fill:#5e81ac"> + transform="rotate(-90,18.433991,1.256939)" + style="fill:#5e81ac"> + transform="matrix(0,-0.65745479,0.3906802,0,16.473516,10.015045)" + style="fill:#5e81ac"> + transform="matrix(0,-0.65745479,-0.3906802,0,14.676196,10.015045)" + style="fill:#5e81ac"> + class="UnoptimicedTransforms" + transform="matrix(1,0,0,0.95938299,0,0.34389071)" /> @@ -104,13 +105,14 @@ + style="fill:#858585"> + transform="matrix(0,-0.65745479,0.3906802,0,19.78396,12.409401)" + style="fill:#858585"> - + transform="matrix(0,-0.65745479,-0.3906802,0,22.251964,12.558589)" + style="fill:#858585"> + transform="rotate(-90,18.433991,1.256939)" + style="fill:#858585"> + transform="matrix(0,-0.65745479,0.3906802,0,16.473516,10.015045)" + style="fill:#858585"> + transform="matrix(0,-0.65745479,-0.3906802,0,14.676196,10.015045)" + style="fill:#858585"> - , #[data(ignore)] current_image: Arc>, + display_state: DisplayState, command_queue: Arc>>, loading_new_image: Arc>, rotating_image: Arc>, current_image_index: usize, current_image_name: String, - image_recenter_required: bool, image_list: Arc>>, druid_event_sink: Arc>, pub dark_theme_enabled: bool, @@ -50,12 +51,12 @@ impl AppState { Self { window_id: None, current_image: Arc::new(Mutex::new(ImageState::Empty)), + display_state: DisplayState::Centered(false), command_queue: Arc::new(Mutex::new(vec![])), loading_new_image: Arc::new(Mutex::new(false)), rotating_image: Arc::new(Mutex::new(false)), current_image_index: 0, current_image_name: String::new(), - image_recenter_required: false, image_list: Arc::new(Mutex::new(Vec::new())), druid_event_sink: Arc::new(Mutex::new(event_sink)), dark_theme_enabled, @@ -80,12 +81,19 @@ impl AppState { ImageState::Empty => false, } } - pub fn get_image_center_state(&self) -> bool { - self.image_recenter_required + + pub fn get_display_state(&self) -> &DisplayState { + &self.display_state + } + + pub fn get_display_state_mut(&mut self) -> &mut DisplayState { + &mut self.display_state } - pub fn set_image_center_state(&mut self, state: bool) { - self.image_recenter_required = state; + + pub fn set_display_state(&mut self, state: DisplayState) { + self.display_state = state } + pub fn set_image_list(&mut self, index: usize, list: Vec) { self.current_image_index = index; self.image_list = Arc::new(Mutex::new(list)); @@ -231,7 +239,11 @@ impl AppState { .into_string() .unwrap(); self.set_current_image_name(image_name); - self.image_recenter_required = true; + // self.set_display_state(DisplayState::Centered(true)); + let event_sink = self.druid_event_sink.lock().unwrap(); + event_sink + .submit_command(RECENTER_IMAGE, Instant::now(), Target::Auto) + .expect("Failed to send command"); } } } @@ -249,7 +261,7 @@ impl AppState { .into_string() .unwrap(); self.set_current_image_name(image_name); - self.image_recenter_required = true; + // self.get_display_state_mut().set(); } pub fn get_image_ref(&self) -> Arc> { self.current_image.clone() @@ -431,7 +443,7 @@ impl AppState { *image_state = ImageState::Empty; self.set_image_list(0, Vec::new()); self.current_image_name = String::new(); - self.image_recenter_required = false; + self.get_display_state_mut().clear(); } pub fn redraw_widgets(&mut self) { diff --git a/src/button_widget.rs b/src/button_widget.rs index 6f640bb..e6bc87b 100644 --- a/src/button_widget.rs +++ b/src/button_widget.rs @@ -3,14 +3,19 @@ use druid::widget::{Svg, SvgData}; use druid::{MouseButton, Point, Selector, Target, WidgetPod}; use std::time::Instant; -pub struct ThemedButton { - command: Option>, - size: Size, - offset: Point, +struct ButtonImageContainer { image: WidgetPod, image_hot: WidgetPod, image_active: WidgetPod, image_disabled: WidgetPod, +} + +pub struct ThemedButton { + active_command: Option>, + command_list: Vec>, + size: Size, + offset: Point, + images: Vec, mask: Vec, is_hot: bool, is_pressed: bool, @@ -19,27 +24,57 @@ pub struct ThemedButton { impl ThemedButton { pub fn new( - command: Option>, + primary_command: Option>, + secondary_command: Option>, size: Size, offset: Point, - image: &str, - image_hot: &str, - image_active: &str, - image_disabled: &str, + image_sources: Vec<&str>, button_mask: Vec, ) -> Self { + let mut command_list: Vec> = Vec::new(); + let mut active_command: Option> = None; + if let Some(command) = primary_command { + command_list.push(command); + active_command = Some(command); + if let Some(command) = secondary_command { + command_list.push(command); + } + } + + let mut images: Vec = Vec::new(); + // TODO: Clean this up + let primary_button_images = ButtonImageContainer { + image: WidgetPod::new(Svg::new(image_sources[0].parse::().unwrap())), + image_hot: WidgetPod::new(Svg::new(image_sources[1].parse::().unwrap())), + image_active: WidgetPod::new(Svg::new(image_sources[2].parse::().unwrap())), + image_disabled: WidgetPod::new(Svg::new(image_sources[3].parse::().unwrap())), + }; + images.push(primary_button_images); + + if command_list.len() > 1 { + let secondary_button_images = ButtonImageContainer { + image: WidgetPod::new(Svg::new(image_sources[4].parse::().unwrap())), + image_hot: WidgetPod::new(Svg::new(image_sources[5].parse::().unwrap())), + image_active: WidgetPod::new(Svg::new( + image_sources[6].parse::().unwrap(), + )), + image_disabled: WidgetPod::new(Svg::new( + image_sources[7].parse::().unwrap(), + )), + }; + images.push(secondary_button_images); + } + Self { - command, + active_command, + command_list, size, offset, - image: WidgetPod::new(Svg::new(image.parse::().unwrap())), - image_hot: WidgetPod::new(Svg::new(image_hot.parse::().unwrap())), - image_active: WidgetPod::new(Svg::new(image_active.parse::().unwrap())), - image_disabled: WidgetPod::new(Svg::new(image_disabled.parse::().unwrap())), + images, mask: button_mask, is_hot: false, is_pressed: false, - is_enabled: matches!(command, Some(_)), + is_enabled: matches!(primary_command, Some(_)), } } pub fn get_offset(&self) -> Point { @@ -51,6 +86,10 @@ impl ThemedButton { pub fn disable(&mut self) { self.is_enabled = false; } + pub fn set_command_index(&mut self, index: usize) { + // TODO: Add bounds checking + self.active_command = Some(self.command_list[index]); + } } impl Widget for ThemedButton { @@ -62,7 +101,7 @@ impl Widget for ThemedButton { let mut event_handled = false; let mut needs_repaint = false; - if let Some(command) = self.command { + if let Some(command) = self.active_command { if let Event::MouseMove(e) = event { let mut x = e.pos.x as usize; x = if x > (self.size.width - 1.) as usize { @@ -128,10 +167,12 @@ impl Widget for ThemedButton { fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &bool, env: &Env) { if let LifeCycle::WidgetAdded = event { - self.image.lifecycle(ctx, event, data, env); - self.image_hot.lifecycle(ctx, event, data, env); - self.image_active.lifecycle(ctx, event, data, env); - self.image_disabled.lifecycle(ctx, event, data, env); + for i in self.images.iter_mut() { + i.image.lifecycle(ctx, event, data, env); + i.image_hot.lifecycle(ctx, event, data, env); + i.image_active.lifecycle(ctx, event, data, env); + i.image_disabled.lifecycle(ctx, event, data, env); + } } if let LifeCycle::FocusChanged(_) | LifeCycle::HotChanged(_) = event { if !ctx.is_active() || !ctx.is_hot() { @@ -150,16 +191,26 @@ impl Widget for ThemedButton { data: &bool, env: &Env, ) -> Size { - self.image.layout(layout_ctx, &bc.loosen(), data, env); - self.image_hot.layout(layout_ctx, &bc.loosen(), data, env); - self.image_active - .layout(layout_ctx, &bc.loosen(), data, env); - self.image_disabled - .layout(layout_ctx, &bc.loosen(), data, env); + for i in self.images.iter_mut() { + i.image.layout(layout_ctx, &bc.loosen(), data, env); + i.image_hot.layout(layout_ctx, &bc.loosen(), data, env); + i.image_active.layout(layout_ctx, &bc.loosen(), data, env); + i.image_disabled.layout(layout_ctx, &bc.loosen(), data, env); + } + self.size } fn paint(&mut self, ctx: &mut PaintCtx, data: &bool, env: &Env) { + let command_index = if let Some(command) = self.active_command { + if command == self.command_list[0] { + 0 + } else { + 1 + } + } else { + 0 + }; let is_button_hot = self.is_hot; let is_context_hot = ctx.is_hot(); let paint_region = *ctx @@ -169,14 +220,16 @@ impl Widget for ThemedButton { .expect("Tried to paint with an invalid clip region"); ctx.with_child_ctx(paint_region, move |f| { - if !self.is_enabled || self.command.is_none() { - self.image_disabled.paint(f, data, env); + if !self.is_enabled || self.active_command.is_none() { + self.images[command_index] + .image_disabled + .paint(f, data, env); } else if self.is_pressed && is_button_hot { - self.image_active.paint(f, data, env); + self.images[command_index].image_active.paint(f, data, env); } else if is_context_hot && is_button_hot { - self.image_hot.paint(f, data, env); + self.images[command_index].image_hot.paint(f, data, env); } else { - self.image.paint(f, data, env); + self.images[command_index].image.paint(f, data, env); } }); } diff --git a/src/commands.rs b/src/commands.rs index f860d46..61337fc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use std::time::Instant; -use crate::types::{Direction, NewImageContainer}; +use crate::types::{Direction, DisplayState, NewImageContainer}; use crate::{platform_api_calls, AppState}; use druid::commands::OPEN_FILE; use druid::{ @@ -26,6 +26,7 @@ pub const IMAGE_ROTATION_COMPLETE: Selector> = pub const ZOOM_IMAGE: Selector = Selector::new("zoom_image"); pub const RECENTER_IMAGE: Selector = Selector::new("recenter_image"); +pub const REALSIZE_IMAGE: Selector = Selector::new("realsize_image"); pub const DELETE_IMAGE: Selector = Selector::new("delete_image"); pub const LOAD_NEW_IMAGE: Selector = Selector::new("load_new_image"); @@ -64,18 +65,22 @@ impl AppDelegate for Delegate { } else if let Some(command_timestamp) = cmd.get(PREV_IMAGE) { data.load_prev_image(command_timestamp); Handled::Yes + } + // The next three events are also partially handled by the ContainerWidget + else if cmd.get(ZOOM_IMAGE).is_some() { + data.set_display_state(DisplayState::Zoomed(true)); + Handled::No } else if cmd.get(RECENTER_IMAGE).is_some() { - data.set_image_center_state(true); - Handled::Yes + data.set_display_state(DisplayState::Centered(true)); + Handled::No + } else if cmd.get(REALSIZE_IMAGE).is_some() { + data.set_display_state(DisplayState::RealSize(true)); + Handled::No } else if cmd.get(FULLSCREEN_VIEW).is_some() { data.show_fullscreen_slideshow(); Handled::Yes } else if cmd.get(DELETE_IMAGE).is_some() { data.delete_image(); - Handled::Yes - } else if cmd.get(ZOOM_IMAGE).is_some() { - println!("Image zoom not yet implemented"); - Handled::Yes } else if let Some(command_timestamp) = cmd.get(ROTATE_LEFT) { data.rotate_in_memory(Direction::Left, command_timestamp); diff --git a/src/container_widget.rs b/src/container_widget.rs index 8d0f3b4..efec2bb 100644 --- a/src/container_widget.rs +++ b/src/container_widget.rs @@ -10,11 +10,13 @@ use druid::{KbKey, Point, Target}; use druid::{Modifiers, Size}; use crate::app_state::*; -use crate::commands::REDRAW_IMAGE; +use crate::commands::{REALSIZE_IMAGE, RECENTER_IMAGE, REDRAW_IMAGE, ZOOM_IMAGE}; +use crate::image_container::ImageState; use crate::image_widget::*; use crate::toolbar_widget::*; use crate::osd_widget::{OSDPayload, OSDWidget}; +use crate::types::DisplayState; use crate::{LOAD_NEW_IMAGE, NEXT_IMAGE, PREV_IMAGE}; // #[derive(Clone, Data)] @@ -63,14 +65,53 @@ impl ContainerWidget { } impl Widget for ContainerWidget { - fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut AppState, _env: &Env) { - let event_sink = _ctx.get_external_handle(); + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, env: &Env) { + let event_sink = ctx.get_external_handle(); - if let Event::Command(cmd) = _event { + if let Event::Command(cmd) = event { if cmd.get(REDRAW_IMAGE).is_some() { - _ctx.request_update(); + ctx.request_update(); + ctx.set_handled(); + } else if cmd.get(ZOOM_IMAGE).is_some() { + let container_size = ctx.size(); + let toolbar_height = data.get_toolbar_height(); + self.image_widget + .widget_mut() + .zoom_image(container_size, toolbar_height); + ctx.request_update(); + ctx.set_handled(); + } else if cmd.get(RECENTER_IMAGE).is_some() { + let image_state_guard = data.get_image_ref(); + let image_state = &mut *image_state_guard.lock().unwrap(); + if let ImageState::Loaded(image_container) = image_state { + let image_size = image_container.get_size(); + let container_size = ctx.size(); + let toolbar_height = data.get_toolbar_height(); + self.image_widget.widget_mut().fit_image( + image_size, + container_size, + toolbar_height, + ); + ctx.request_update(); + ctx.set_handled(); + } + } else if cmd.get(REALSIZE_IMAGE).is_some() { + let image_state_guard = data.get_image_ref(); + let image_state = &mut *image_state_guard.lock().unwrap(); + if let ImageState::Loaded(image_container) = image_state { + let image_size = image_container.get_size(); + let container_size = ctx.size(); + let toolbar_height = data.get_toolbar_height(); + self.image_widget.widget_mut().realsize_image( + image_size, + container_size, + toolbar_height, + ); + ctx.request_update(); + ctx.set_handled(); + } } - } else if let Event::KeyDown(k) = _event { + } else if let Event::KeyDown(k) = event { // Key events are always handled here in the container if k.key == KbKey::ArrowRight { event_sink @@ -88,36 +129,36 @@ impl Widget for ContainerWidget { } else if let Event::MouseDown(e) | Event::MouseUp(e) | Event::MouseMove(e) - | Event::Wheel(e) = _event + | Event::Wheel(e) = event { - if !_data.has_image() { - self.osd_widget.event(_ctx, _event, _data, _env); + if !data.has_image() { + self.osd_widget.event(ctx, event, data, env); } // Mouse events will be handled by either the toolbar or the image widget - if e.window_pos.y < _ctx.size().height - _data.get_toolbar_height() { - _ctx.set_focus(self.image_widget.id()); - self.image_widget.event(_ctx, _event, _data, _env); + if e.window_pos.y < ctx.size().height - data.get_toolbar_height() { + ctx.set_focus(self.image_widget.id()); + self.image_widget.event(ctx, event, data, env); if e.button.is_left() || e.wheel_delta != Vec2::ZERO { - _data.set_image_center_state(false); + data.set_display_state(DisplayState::Zoomed(false)); } } else { - _ctx.set_focus(self.toolbar_widget.id()); - self.toolbar_widget.event(_ctx, _event, _data, _env); + ctx.set_focus(self.toolbar_widget.id()); + self.toolbar_widget.event(ctx, event, data, env); } - } else if let Event::Zoom(_e) = _event { - _ctx.set_focus(self.image_widget.id()); - self.image_widget.event(_ctx, _event, _data, _env); - _data.set_image_center_state(false); - } else if let Event::Internal(_e) = _event { - self.image_widget.event(_ctx, _event, _data, _env); - self.toolbar_widget.event(_ctx, _event, _data, _env); - } else if let Event::WindowConnected = _event { - } else if let Event::WindowSize(_e) = _event { + } else if let Event::Zoom(_e) = event { + ctx.set_focus(self.image_widget.id()); + self.image_widget.event(ctx, event, data, env); + data.set_display_state(DisplayState::Zoomed(false)); + } else if let Event::Internal(_e) = event { + self.image_widget.event(ctx, event, data, env); + self.toolbar_widget.event(ctx, event, data, env); + } else if let Event::WindowConnected = event { + } else if let Event::WindowSize(_e) = event { } else { - self.image_widget.event(_ctx, _event, _data, _env); - self.toolbar_widget.event(_ctx, _event, _data, _env); + self.image_widget.event(ctx, event, data, env); + self.toolbar_widget.event(ctx, event, data, env); } } @@ -135,20 +176,24 @@ impl Widget for ContainerWidget { self.osd_widget.lifecycle(_ctx, _event, _data, _env); } - fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &AppState, _data: &AppState, _env: &Env) { - self.toolbar_widget.update(_ctx, _data, _env); + fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &AppState, data: &AppState, _env: &Env) { + self.toolbar_widget.update(_ctx, data, _env); - let mut needs_paint = true; // repaint on all updates, for now + let needs_paint = true; // repaint on all updates, for now - if _data.get_image_center_state() && !_old_data.get_image_center_state() { - self.image_widget - .widget_mut() - .update(_ctx, _old_data, _data, _env); - needs_paint = true; - } + // if data.get_image_center_state() && !old_data.get_image_center_state() { + // self.image_widget + // .widget_mut() + // .update(_ctx, old_data, data, _env); + // needs_paint = true; + // } + + // if data.get_display_state() != old_data.get_display_state() { + // println!("Display state: {:#?}", data.get_display_state()); + // } if needs_paint { - let new_window_title = String::from("Foxfire - ") + &_data.get_image_name(); + let new_window_title = String::from("Foxfire - ") + &data.get_image_name(); _ctx.window().set_title(&new_window_title); self.blur_cache = None; _ctx.children_changed(); diff --git a/src/image_widget.rs b/src/image_widget.rs index cafec3f..a7a8b19 100644 --- a/src/image_widget.rs +++ b/src/image_widget.rs @@ -10,68 +10,102 @@ use crate::image_container::ImageState; use crate::types::*; pub struct ImageWidget { - center: bool, transform: Option, } impl ImageWidget { pub fn new() -> Self { - Self { - center: true, - transform: None, - } - } - - pub fn get_centered_state(&self) -> bool { - self.center - } - pub fn set_centered_state(&mut self, state: bool) { - self.center = state; - } - - pub fn clear_transform(&mut self) { - self.transform = None; + Self { transform: None } } - pub fn center_image(&mut self, image: Size, container: Size, unscaled_toolbar_offset: f64) { + pub fn fit_image(&mut self, image: Size, container: Size, unscaled_toolbar_offset: f64) { let mut image_transformation = ImageTransformation::new(); let image_aspect_ratio = image.width / image.height; let container_aspect_ratio = container.width / (container.height - unscaled_toolbar_offset); let scale_factor: f64; - let centering_vector: Vec2D; - - if image_aspect_ratio > container_aspect_ratio { + let centering_vector: Vec2D = if image_aspect_ratio > container_aspect_ratio { // the image is wider than the container, so match the widths to fill scale_factor = container.width / image.width; - centering_vector = Vec2D::from( + Vec2D::from( 0., (container.height - unscaled_toolbar_offset) / 2. - (image.height * scale_factor) / 2., - ); + ) } else { // the image is wider than the container, so fit the heights scale_factor = (container.height - unscaled_toolbar_offset) / image.height; - centering_vector = - Vec2D::from(container.width / 2. - (image.width * scale_factor) / 2., 0.); - } + Vec2D::from(container.width / 2. - (image.width * scale_factor) / 2., 0.) + }; - image_transformation.set_screen_space_offset(centering_vector); + image_transformation.set_offset(centering_vector); image_transformation.set_scale(scale_factor); self.transform = Some(image_transformation); } + + pub fn recenter_image(&mut self, image: Size, container: Size, unscaled_toolbar_offset: f64) { + let image_center: Vec2D = Vec2D::from(image.width / 2., image.height / 2.); + let container_center: Vec2D = Vec2D::from( + container.width / 2., + (container.height - unscaled_toolbar_offset) / 2., + ); + + let mut new_transform = ImageTransformation::new(); + + new_transform.set_offset(container_center - image_center); + + self.transform = Some(new_transform); + } + + pub fn realsize_image(&mut self, image: Size, container: Size, unscaled_toolbar_offset: f64) { + let image_center: Vec2D = Vec2D::from(image.width / 2., image.height / 2.); + let container_center: Vec2D = Vec2D::from( + container.width / 2., + (container.height - unscaled_toolbar_offset) / 2., + ); + + let mut new_transform = ImageTransformation::new(); + + new_transform.set_offset(container_center - image_center); + + self.transform = Some(new_transform); + } + + pub fn zoom_image(&mut self, container: Size, unscaled_toolbar_offset: f64) { + let transform = self.transform.expect("Bad state"); + let old_scale_factor = 1. / transform.get_scale(); + + let offset_vector: Vec2D = transform.get_offset(); + let container_center: Vec2D = Vec2D::from( + container.width / 2., + (container.height - unscaled_toolbar_offset) / 2., + ); + + let new_scale_factor: f64 = 1.25 / (old_scale_factor); + let mut new_transform = ImageTransformation::new(); + + new_transform.set_offset( + container_center + - (-offset_vector * Vec2D::from_single(old_scale_factor) + + container_center * Vec2D::from_single(old_scale_factor)) + * Vec2D::from_single(new_scale_factor), + ); + new_transform.set_scale(new_scale_factor); + + self.transform = Some(new_transform); + } } impl Widget for ImageWidget { - fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut AppState, _env: &Env) { - let has_image = _data.has_image(); - let has_image_error = _data.has_image_error(); + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, _env: &Env) { + let has_image = data.has_image(); + let has_image_error = data.has_image_error(); - let image_state_guard = _data.get_image_ref(); + let image_state_guard = data.get_image_ref(); let image_state = &mut *image_state_guard.lock().unwrap(); if let ImageState::Loaded(image_container) = image_state { - if let Event::Wheel(mouse_event) = _event { + if let Event::Wheel(mouse_event) = event { if image_container.event_queue.is_none() { let mouse_position = Vec2D::from(mouse_event.window_pos.x, mouse_event.window_pos.y); @@ -79,45 +113,45 @@ impl Widget for ImageWidget { mouse_event.wheel_delta.y, mouse_position, ))); - self.set_centered_state(false); + // self.set_centered_state(false); } - _ctx.request_paint(); - } else if let Event::MouseDown(mouse_event) = _event { + ctx.request_paint(); + } else if let Event::MouseDown(mouse_event) = event { if image_container.event_queue.is_none() { let mouse_pos = Vec2D::from(mouse_event.window_pos.x, mouse_event.window_pos.y); if mouse_event.button.is_left() { let new_drag_event = DragEvent::new(mouse_pos, false); image_container.event_queue = Some(MouseEvent::Drag(new_drag_event)); // _ctx.set_cursor(&Cursor::Crosshair); - self.set_centered_state(false); + // self.set_centered_state(false); } else if mouse_event.button.is_right() { let context_menu = generate_menu(has_image, has_image_error); - _ctx.show_context_menu(context_menu, mouse_event.pos) + ctx.show_context_menu(context_menu, mouse_event.pos) } } - _ctx.request_paint(); - } else if let Event::MouseMove(mouse_event) = _event { + ctx.request_paint(); + } else if let Event::MouseMove(mouse_event) = event { if let Some(MouseEvent::Drag(drag_event)) = &mut image_container.event_queue { if !drag_event.is_finished() { let current_pos = Vec2D::from(mouse_event.window_pos.x, mouse_event.window_pos.y); drag_event.set_delta(current_pos); - _ctx.request_paint(); + ctx.request_paint(); } } - } else if let Event::MouseUp(_mouse_event) = _event { + } else if let Event::MouseUp(_mouse_event) = event { if let Some(active_event) = &mut image_container.event_queue { if let MouseEvent::Drag(drag_event) = active_event { drag_event.complete(); } - _ctx.request_paint(); + ctx.request_paint(); } - } else if let Event::WindowSize(_) = _event { + } else if let Event::WindowSize(_) = event { } - } else if let Event::MouseDown(mouse_event) = _event { + } else if let Event::MouseDown(mouse_event) = event { if mouse_event.button.is_right() { let context_menu = generate_menu(has_image, has_image_error); - _ctx.show_context_menu(context_menu, mouse_event.pos) + ctx.show_context_menu(context_menu, mouse_event.pos) } } } @@ -160,21 +194,17 @@ impl Widget for ImageWidget { } } - fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &AppState, _data: &AppState, _env: &Env) { - if _data.get_image_center_state() { - self.set_centered_state(true); - self.clear_transform(); - } - let image_state_guard = _data.get_image_ref(); + fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &AppState, data: &AppState, _env: &Env) { + let image_state_guard = data.get_image_ref(); let image_state = &mut *image_state_guard.lock().unwrap(); if let ImageState::Loaded(image_container) = image_state { if image_container.event_queue.is_some() { if let Some(MouseEvent::Drag(drag_event)) = &mut image_container.event_queue { if drag_event.is_finished() { - _ctx.set_cursor(&Cursor::Arrow); + ctx.set_cursor(&Cursor::Arrow); } else if drag_event.is_new() { drag_event.mark_seen(); - _ctx.set_cursor(&Cursor::Crosshair); + ctx.set_cursor(&Cursor::Crosshair); } } } @@ -185,19 +215,31 @@ impl Widget for ImageWidget { &mut self, _layout_ctx: &mut LayoutCtx, bc: &BoxConstraints, - _data: &AppState, + data: &AppState, _env: &Env, ) -> Size { - if self.get_centered_state() { - let image_state_guard = _data.get_image_ref(); - let image_state = &mut *image_state_guard.lock().unwrap(); - if let ImageState::Loaded(image_container) = image_state { + let image_state_guard = data.get_image_ref(); + let image_state = &mut *image_state_guard.lock().unwrap(); + if let ImageState::Loaded(image_container) = image_state { + let current_display_state = data.get_display_state(); + if let DisplayState::Centered(true) = current_display_state { let image_size = image_container.get_size(); let container_size = bc.max(); - let toolbar_height = _data.get_toolbar_height(); - - self.center_image(image_size, container_size, toolbar_height); - } + let toolbar_height = data.get_toolbar_height(); + self.fit_image(image_size, container_size, toolbar_height); + } else if let DisplayState::RealSize(true) = current_display_state { + let image_size = image_container.get_size(); + let container_size = bc.max(); + let toolbar_height = data.get_toolbar_height(); + self.realsize_image(image_size, container_size, toolbar_height); + } + // else { + // let image_size = image_container.get_size(); + // let container_size = bc.max(); + // let toolbar_height = data.get_toolbar_height(); + // self.recenter_image(image_size, container_size, toolbar_height); + // println!("HIT"); + // } } if bc.is_width_bounded() && bc.is_height_bounded() { @@ -235,7 +277,7 @@ impl Widget for ImageWidget { } if self.transform.is_none() { - self.center_image(image_size, container_size, data.get_toolbar_height()); + self.fit_image(image_size, container_size, data.get_toolbar_height()); } let mut image_transform = self .transform @@ -244,12 +286,12 @@ impl Widget for ImageWidget { const IMAGE_ORIGIN_NAMESPACE: Vec2D = Vec2D { x: 0.0, y: 0.0 }; let image_corner_imagespace = IMAGE_ORIGIN_NAMESPACE + Vec2D::from(image_size.width, image_size.height); - let mut drag_offset_screenspace = image_transform.screen_space_offset; + let mut drag_offset_screenspace = image_transform.offset; if let Some(MouseEvent::Drag(drag_event)) = &image_container.event_queue { drag_offset_screenspace.x += drag_event.get_delta().x; drag_offset_screenspace.y += drag_event.get_delta().y; if drag_event.is_finished() { - image_transform.screen_space_offset = drag_offset_screenspace; + image_transform.offset = drag_offset_screenspace; image_container.event_queue = None; } } else if let Some(MouseEvent::Zoom(zoom_event)) = &image_container.event_queue { @@ -257,28 +299,24 @@ impl Widget for ImageWidget { let cursor_position = zoom_event.get_position(); let cursor_vec = Vec2D::from(cursor_position.x, cursor_position.y); let zoom_target_prescale = image_transform.affine_matrix.inverse() - * (cursor_vec - drag_offset_screenspace) - + image_transform.image_space_offset; + * (cursor_vec - drag_offset_screenspace); - image_transform.affine_matrix.scale(1. + zoom_factor); + image_transform.affine_matrix.set_scale(1. + zoom_factor); let zoom_target_postscale = image_transform.affine_matrix.inverse() - * (cursor_vec - drag_offset_screenspace) - + image_transform.image_space_offset; + * (cursor_vec - drag_offset_screenspace); drag_offset_screenspace = drag_offset_screenspace + image_transform.affine_matrix * (zoom_target_postscale - zoom_target_prescale); - image_transform.screen_space_offset = drag_offset_screenspace; + image_transform.offset = drag_offset_screenspace; image_container.event_queue = None; } - let image_origin_screenspace = image_transform.affine_matrix - * (IMAGE_ORIGIN_NAMESPACE - image_transform.image_space_offset) - + drag_offset_screenspace; - let image_corner_screenspace = image_transform.affine_matrix - * (image_corner_imagespace - image_transform.image_space_offset) - + drag_offset_screenspace; + let image_origin_screenspace = + image_transform.affine_matrix * (IMAGE_ORIGIN_NAMESPACE) + drag_offset_screenspace; + let image_corner_screenspace = + image_transform.affine_matrix * (image_corner_imagespace) + drag_offset_screenspace; let image_viewport = Rect::new( IMAGE_ORIGIN_NAMESPACE.x, diff --git a/src/toolbar_widget.rs b/src/toolbar_widget.rs index fe58cdd..ef2918c 100644 --- a/src/toolbar_widget.rs +++ b/src/toolbar_widget.rs @@ -1,9 +1,11 @@ use crate::app_state::AppState; use crate::button_widget::*; use crate::commands::{ - DELETE_IMAGE, NEXT_IMAGE, PREV_IMAGE, RECENTER_IMAGE, ROTATE_LEFT, ROTATE_RIGHT, + DELETE_IMAGE, NEXT_IMAGE, PREV_IMAGE, REALSIZE_IMAGE, RECENTER_IMAGE, ROTATE_LEFT, + ROTATE_RIGHT, ZOOM_IMAGE, }; -use crate::{FULLSCREEN_VIEW, TOGGLE_BLUR}; +use crate::types::DisplayState; +use crate::TOGGLE_BLUR; use druid::widget::prelude::*; use druid::widget::Svg; use druid::widget::SvgData; @@ -21,97 +23,133 @@ impl ToolbarWidget { let mut buttons = Vec::new(); let zoom_button = WidgetPod::new(ThemedButton::new( + Some(ZOOM_IMAGE), None, Size::new(32., 32.), Point::new(8. + 68. + 2. * 32. + 6. * 4., 16.), - include_str!("../resources/buttons/zoom/button.svg"), - include_str!("../resources/buttons/zoom/hot.svg"), - include_str!("../resources/buttons/zoom/active.svg"), - include_str!("../resources/buttons/zoom/disabled.svg"), + [ + include_str!("../resources/buttons/zoom/button.svg"), + include_str!("../resources/buttons/zoom/hot.svg"), + include_str!("../resources/buttons/zoom/active.svg"), + include_str!("../resources/buttons/zoom/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/generic/small_button_mask").to_vec(), )); buttons.push(zoom_button); let recenter_button = WidgetPod::new(ThemedButton::new( Some(RECENTER_IMAGE), + Some(REALSIZE_IMAGE), Size::new(32., 32.), Point::new(8. + 68. + 1. * 32. + 5. * 4., 16.), - include_str!("../resources/buttons/recenter/button.svg"), - include_str!("../resources/buttons/recenter/hot.svg"), - include_str!("../resources/buttons/recenter/active.svg"), - include_str!("../resources/buttons/recenter/disabled.svg"), + [ + include_str!("../resources/buttons/recenter/button.svg"), + include_str!("../resources/buttons/recenter/hot.svg"), + include_str!("../resources/buttons/recenter/active.svg"), + include_str!("../resources/buttons/recenter/disabled.svg"), + include_str!("../resources/buttons/realsize/button.svg"), + include_str!("../resources/buttons/realsize/hot.svg"), + include_str!("../resources/buttons/realsize/active.svg"), + include_str!("../resources/buttons/realsize/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/generic/small_button_mask").to_vec(), )); buttons.push(recenter_button); let prev_button = WidgetPod::new(ThemedButton::new( Some(PREV_IMAGE), + None, Size::new(68., 32.), Point::new(32. + 58., 16.), - include_str!("../resources/buttons/prev/button.svg"), - include_str!("../resources/buttons/prev/hot.svg"), - include_str!("../resources/buttons/prev/active.svg"), - include_str!("../resources/buttons/prev/disabled.svg"), + [ + include_str!("../resources/buttons/prev/button.svg"), + include_str!("../resources/buttons/prev/hot.svg"), + include_str!("../resources/buttons/prev/active.svg"), + include_str!("../resources/buttons/prev/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/prev/mask").to_vec(), )); buttons.push(prev_button); let fullscreen_button = WidgetPod::new(ThemedButton::new( + None, None, Size::new(64., 64.), Point::new(32., 32.), - include_str!("../resources/buttons/fullscreen/button.svg"), - include_str!("../resources/buttons/fullscreen/hot.svg"), - include_str!("../resources/buttons/fullscreen/active.svg"), - include_str!("../resources/buttons/fullscreen/disabled.svg"), + [ + include_str!("../resources/buttons/fullscreen/button.svg"), + include_str!("../resources/buttons/fullscreen/hot.svg"), + include_str!("../resources/buttons/fullscreen/active.svg"), + include_str!("../resources/buttons/fullscreen/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/fullscreen/mask").to_vec(), )); buttons.push(fullscreen_button); let next_button = WidgetPod::new(ThemedButton::new( Some(NEXT_IMAGE), + None, Size::new(68., 32.), Point::new(32. - 54., 16.), - include_str!("../resources/buttons/next/button.svg"), - include_str!("../resources/buttons/next/hot.svg"), - include_str!("../resources/buttons/next/active.svg"), - include_str!("../resources/buttons/next/disabled.svg"), + [ + include_str!("../resources/buttons/next/button.svg"), + include_str!("../resources/buttons/next/hot.svg"), + include_str!("../resources/buttons/next/active.svg"), + include_str!("../resources/buttons/next/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/next/mask").to_vec(), )); buttons.push(next_button); let rot_l_button = WidgetPod::new(ThemedButton::new( Some(ROTATE_LEFT), + None, Size::new(32., 32.), Point::new(8. - (68. + 1. * 32. + 1. * 4.), 16.), - include_str!("../resources/buttons/rot_l/button.svg"), - include_str!("../resources/buttons/rot_l/hot.svg"), - include_str!("../resources/buttons/rot_l/active.svg"), - include_str!("../resources/buttons/rot_l/disabled.svg"), + [ + include_str!("../resources/buttons/rot_l/button.svg"), + include_str!("../resources/buttons/rot_l/hot.svg"), + include_str!("../resources/buttons/rot_l/active.svg"), + include_str!("../resources/buttons/rot_l/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/generic/small_button_mask").to_vec(), )); buttons.push(rot_l_button); let rot_r_button = WidgetPod::new(ThemedButton::new( Some(ROTATE_RIGHT), + None, Size::new(32., 32.), Point::new(8. - (68. + 2. * 32. + 2. * 4.), 16.), - include_str!("../resources/buttons/rot_r/button.svg"), - include_str!("../resources/buttons/rot_r/hot.svg"), - include_str!("../resources/buttons/rot_r/active.svg"), - include_str!("../resources/buttons/rot_r/disabled.svg"), + [ + include_str!("../resources/buttons/rot_r/button.svg"), + include_str!("../resources/buttons/rot_r/hot.svg"), + include_str!("../resources/buttons/rot_r/active.svg"), + include_str!("../resources/buttons/rot_r/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/generic/small_button_mask").to_vec(), )); buttons.push(rot_r_button); let delete_button = WidgetPod::new(ThemedButton::new( Some(DELETE_IMAGE), + None, Size::new(32., 32.), Point::new(8. - (68. + 3. * 32. + 4. * 4.), 16.), - include_str!("../resources/buttons/del/button.svg"), - include_str!("../resources/buttons/del/hot.svg"), - include_str!("../resources/buttons/del/active.svg"), - include_str!("../resources/buttons/del/disabled.svg"), + [ + include_str!("../resources/buttons/del/button.svg"), + include_str!("../resources/buttons/del/hot.svg"), + include_str!("../resources/buttons/del/active.svg"), + include_str!("../resources/buttons/del/disabled.svg"), + ] + .to_vec(), include_bytes!("../resources/buttons/generic/small_button_mask").to_vec(), )); buttons.push(delete_button); @@ -174,19 +212,26 @@ impl Widget for ToolbarWidget { } } - fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &AppState, _data: &AppState, _env: &Env) { + fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &AppState, data: &AppState, _env: &Env) { // Not efficient, but temporary until we find a way to not miss updates - if _data.has_image() && !_data.has_image_error() { + if data.has_image() && !data.has_image_error() { for button in self.buttons.iter_mut() { button.widget_mut().enable(); } - if _data.get_image_list_size() == 1 { + if data.get_image_list_size() == 1 { // Disable the next & previous buttons if there is only one image self.buttons[2].widget_mut().disable(); self.buttons[4].widget_mut().disable(); } - if _data.get_image_center_state() { - self.buttons[1].widget_mut().disable(); + // TODO: Fix this to work with the new state tracking + // if _data.get_image_center_state() { + // self.buttons[1].widget_mut().disable(); + // } + // let display_state = data.get_display_state(); + match data.get_display_state() { + DisplayState::Centered(_) => self.buttons[1].widget_mut().set_command_index(1), + DisplayState::RealSize(_) => self.buttons[1].widget_mut().set_command_index(0), + DisplayState::Zoomed(_) => self.buttons[1].widget_mut().set_command_index(0), } } else { for button in self.buttons.iter_mut() { diff --git a/src/types.rs b/src/types.rs index 0179202..ef2204a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,23 +7,27 @@ use image::DynamicImage; #[derive(Debug, Copy, Clone)] pub struct ImageTransformation { pub affine_matrix: Matrix2x2, - pub image_space_offset: Vec2D, - pub screen_space_offset: Vec2D, + pub offset: Vec2D, } impl ImageTransformation { pub fn new() -> Self { ImageTransformation { affine_matrix: Matrix2x2::new(), - image_space_offset: Vec2D::new(), - screen_space_offset: Vec2D::new(), + offset: Vec2D::new(), } } + pub fn get_scale(&self) -> f64 { + self.affine_matrix.get_scale() + } pub fn set_scale(&mut self, scale_factor: f64) { - self.affine_matrix.scale(scale_factor); + self.affine_matrix.set_scale(scale_factor); + } + pub fn set_offset(&mut self, new_offset: Vec2D) { + self.offset = new_offset; } - pub fn set_screen_space_offset(&mut self, new_offset: Vec2D) { - self.screen_space_offset = new_offset; + pub fn get_offset(&self) -> Vec2D { + self.offset } } @@ -32,13 +36,38 @@ pub enum Direction { Right, } +#[derive(Clone, Data, Debug)] +pub enum DisplayState { + Centered(bool), + RealSize(bool), + Zoomed(bool), +} + +impl DisplayState { + pub fn set(&mut self) -> &mut Self { + self.set_state(true) + } + pub fn clear(&mut self) -> &mut Self { + self.set_state(false) + } + + fn set_state(&mut self, new_state: bool) -> &mut Self { + match self { + DisplayState::Centered(ref mut old_state) => *old_state = new_state, + DisplayState::RealSize(ref mut old_state) => *old_state = new_state, + DisplayState::Zoomed(ref mut old_state) => *old_state = new_state, + }; + self + } +} + #[derive(Debug, Copy, Clone, Data)] pub struct Vec2D { pub x: T, pub y: T, } -impl> Vec2D { +impl + Copy> Vec2D { pub fn new() -> Self { Self { x: T::from(0.0), @@ -48,6 +77,10 @@ impl> Vec2D { pub fn from(x: T, y: T) -> Self { Self { x, y } } + + pub fn from_single(w: T) -> Self { + Self { x: w, y: w } + } } impl Add> for Vec2D @@ -61,6 +94,16 @@ where } } +impl AddAssign> for Vec2D +where + T: From + Add + Add + Copy + std::ops::AddAssign, +{ + fn add_assign(&mut self, rhs: Vec2D) { + self.x += rhs.x; + self.y += rhs.y; + } +} + impl Sub> for Vec2D where T: From + Sub + Sub + Copy, @@ -72,6 +115,39 @@ where } } +impl Neg for Vec2D +where + T: From + Neg + Neg + Copy, +{ + type Output = Vec2D; + + fn neg(self) -> Self::Output { + Vec2D::from(-self.x, -self.y) + } +} + +impl Mul> for Vec2D +where + T: From + Sub + Sub + Copy + std::ops::Mul, +{ + type Output = Vec2D; + + fn mul(self, rhs: Vec2D) -> Self::Output { + Vec2D::from(self.x * rhs.x, self.y * rhs.y) + } +} + +impl Div> for Vec2D +where + T: From + Sub + Sub + Copy + std::ops::Div, +{ + type Output = Vec2D; + + fn div(self, rhs: Vec2D) -> Self::Output { + Vec2D::from(self.x / rhs.x, self.y / rhs.y) + } +} + #[derive(Debug, Copy, Clone)] pub struct Matrix2x2 { pub a: T, @@ -99,7 +175,10 @@ impl< d: T::from(1.0), } } - pub fn scale(&mut self, scale_factor: T) { + pub fn get_scale(&self) -> T { + self.a + } + pub fn set_scale(&mut self, scale_factor: T) { let new_scale: T = self.a * scale_factor; if new_scale <= T::from(100.0) && new_scale >= T::from(0.01) { self.a = new_scale;