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