From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- native/src/widget/image/viewer.rs | 428 -------------------------------------- 1 file changed, 428 deletions(-) delete mode 100644 native/src/widget/image/viewer.rs (limited to 'native/src/widget/image/viewer.rs') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs deleted file mode 100644 index 1f8d5d7a..00000000 --- a/native/src/widget/image/viewer.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! Zoom and pan on an image. -use crate::event::{self, Event}; -use crate::image; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, -}; - -use std::hash::Hash; - -/// A frame that displays an image with the ability to zoom in/out and pan. -#[allow(missing_debug_implementations)] -pub struct Viewer { - padding: f32, - width: Length, - height: Length, - min_scale: f32, - max_scale: f32, - scale_step: f32, - handle: Handle, -} - -impl Viewer { - /// Creates a new [`Viewer`] with the given [`State`]. - pub fn new(handle: Handle) -> Self { - Viewer { - padding: 0.0, - width: Length::Shrink, - height: Length::Shrink, - min_scale: 0.25, - max_scale: 10.0, - scale_step: 0.10, - handle, - } - } - - /// Sets the padding of the [`Viewer`]. - pub fn padding(mut self, padding: impl Into) -> Self { - self.padding = padding.into().0; - self - } - - /// Sets the width of the [`Viewer`]. - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Viewer`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - /// Sets the max scale applied to the image of the [`Viewer`]. - /// - /// Default is `10.0` - pub fn max_scale(mut self, max_scale: f32) -> Self { - self.max_scale = max_scale; - self - } - - /// Sets the min scale applied to the image of the [`Viewer`]. - /// - /// Default is `0.25` - pub fn min_scale(mut self, min_scale: f32) -> Self { - self.min_scale = min_scale; - self - } - - /// Sets the percentage the image of the [`Viewer`] will be scaled by - /// when zoomed in / out. - /// - /// Default is `0.10` - pub fn scale_step(mut self, scale_step: f32) -> Self { - self.scale_step = scale_step; - self - } -} - -impl Widget for Viewer -where - Renderer: image::Renderer, - Handle: Clone + Hash, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let Size { width, height } = renderer.dimensions(&self.handle); - - let mut size = limits - .width(self.width) - .height(self.height) - .resolve(Size::new(width as f32, height as f32)); - - let expansion_size = if height > width { - self.width - } else { - self.height - }; - - // Only calculate viewport sizes if the images are constrained to a limited space. - // If they are Fill|Portion let them expand within their alotted space. - match expansion_size { - Length::Shrink | Length::Fixed(_) => { - let aspect_ratio = width as f32 / height as f32; - let viewport_aspect_ratio = size.width / size.height; - if viewport_aspect_ratio > aspect_ratio { - size.width = width as f32 * size.height / height as f32; - } else { - size.height = height as f32 * size.width / width as f32; - } - } - Length::Fill | Length::FillPortion(_) => {} - } - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) - if is_mouse_over => - { - match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - let state = tree.state.downcast_mut::(); - let previous_scale = state.scale; - - if y < 0.0 && previous_scale > self.min_scale - || y > 0.0 && previous_scale < self.max_scale - { - state.scale = (if y > 0.0 { - state.scale * (1.0 + self.scale_step) - } else { - state.scale / (1.0 + self.scale_step) - }) - .clamp(self.min_scale, self.max_scale); - - let image_size = image_size( - renderer, - &self.handle, - state, - bounds.size(), - ); - - let factor = state.scale / previous_scale - 1.0; - - let cursor_to_center = - cursor_position - bounds.center(); - - let adjustment = cursor_to_center * factor - + state.current_offset * factor; - - state.current_offset = Vector::new( - if image_size.width > bounds.width { - state.current_offset.x + adjustment.x - } else { - 0.0 - }, - if image_size.height > bounds.height { - state.current_offset.y + adjustment.y - } else { - 0.0 - }, - ); - } - } - } - - event::Status::Captured - } - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - if is_mouse_over => - { - let state = tree.state.downcast_mut::(); - - state.cursor_grabbed_at = Some(cursor_position); - state.starting_offset = state.current_offset; - - event::Status::Captured - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { - let state = tree.state.downcast_mut::(); - - if state.cursor_grabbed_at.is_some() { - state.cursor_grabbed_at = None; - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Mouse(mouse::Event::CursorMoved { position }) => { - let state = tree.state.downcast_mut::(); - - if let Some(origin) = state.cursor_grabbed_at { - let image_size = image_size( - renderer, - &self.handle, - state, - bounds.size(), - ); - - let hidden_width = (image_size.width - bounds.width / 2.0) - .max(0.0) - .round(); - - let hidden_height = (image_size.height - - bounds.height / 2.0) - .max(0.0) - .round(); - - let delta = position - origin; - - let x = if bounds.width < image_size.width { - (state.starting_offset.x - delta.x) - .clamp(-hidden_width, hidden_width) - } else { - 0.0 - }; - - let y = if bounds.height < image_size.height { - (state.starting_offset.y - delta.y) - .clamp(-hidden_height, hidden_height) - } else { - 0.0 - }; - - state.current_offset = Vector::new(x, y); - - event::Status::Captured - } else { - event::Status::Ignored - } - } - _ => event::Status::Ignored, - } - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let state = tree.state.downcast_ref::(); - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - if state.is_cursor_grabbed() { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::Idle - } - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - _theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let state = tree.state.downcast_ref::(); - let bounds = layout.bounds(); - - let image_size = - image_size(renderer, &self.handle, state, bounds.size()); - - let translation = { - let image_top_left = Vector::new( - bounds.width / 2.0 - image_size.width / 2.0, - bounds.height / 2.0 - image_size.height / 2.0, - ); - - image_top_left - state.offset(bounds, image_size) - }; - - renderer.with_layer(bounds, |renderer| { - renderer.with_translation(translation, |renderer| { - image::Renderer::draw( - renderer, - self.handle.clone(), - Rectangle { - x: bounds.x, - y: bounds.y, - ..Rectangle::with_size(image_size) - }, - ) - }); - }); - } -} - -/// The local state of a [`Viewer`]. -#[derive(Debug, Clone, Copy)] -pub struct State { - scale: f32, - starting_offset: Vector, - current_offset: Vector, - cursor_grabbed_at: Option, -} - -impl Default for State { - fn default() -> Self { - Self { - scale: 1.0, - starting_offset: Vector::default(), - current_offset: Vector::default(), - cursor_grabbed_at: None, - } - } -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> Self { - State::default() - } - - /// Returns the current offset of the [`State`], given the bounds - /// of the [`Viewer`] and its image. - fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { - let hidden_width = - (image_size.width - bounds.width / 2.0).max(0.0).round(); - - let hidden_height = - (image_size.height - bounds.height / 2.0).max(0.0).round(); - - Vector::new( - self.current_offset.x.clamp(-hidden_width, hidden_width), - self.current_offset.y.clamp(-hidden_height, hidden_height), - ) - } - - /// Returns if the cursor is currently grabbed by the [`Viewer`]. - pub fn is_cursor_grabbed(&self) -> bool { - self.cursor_grabbed_at.is_some() - } -} - -impl<'a, Message, Renderer, Handle> From> - for Element<'a, Message, Renderer> -where - Renderer: 'a + image::Renderer, - Message: 'a, - Handle: Clone + Hash + 'a, -{ - fn from(viewer: Viewer) -> Element<'a, Message, Renderer> { - Element::new(viewer) - } -} - -/// Returns the bounds of the underlying image, given the bounds of -/// the [`Viewer`]. Scaling will be applied and original aspect ratio -/// will be respected. -pub fn image_size( - renderer: &Renderer, - handle: &::Handle, - state: &State, - bounds: Size, -) -> Size -where - Renderer: image::Renderer, -{ - let Size { width, height } = renderer.dimensions(handle); - - let (width, height) = { - let dimensions = (width as f32, height as f32); - - let width_ratio = bounds.width / dimensions.0; - let height_ratio = bounds.height / dimensions.1; - - let ratio = width_ratio.min(height_ratio); - let scale = state.scale; - - if ratio < 1.0 { - (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale) - } else { - (dimensions.0 * scale, dimensions.1 * scale) - } - }; - - Size::new(width, height) -} -- cgit