11//! Zoom and pan on an image.
22use crate :: core:: event:: { self , Event } ;
3- use crate :: core:: image;
3+ use crate :: core:: image:: { self , FilterMethod } ;
44use crate :: core:: layout;
55use crate :: core:: mouse;
66use crate :: core:: renderer;
77use crate :: core:: widget:: tree:: { self , Tree } ;
88use crate :: core:: {
9- Clipboard , Element , Layout , Length , Pixels , Point , Radians , Rectangle ,
10- Shell , Size , Vector , Widget ,
9+ Clipboard , ContentFit , Element , Layout , Length , Pixels , Point , Radians ,
10+ Rectangle , Shell , Size , Vector , Widget ,
1111} ;
1212
1313/// A frame that displays an image with the ability to zoom in/out and pan.
@@ -20,30 +20,38 @@ pub struct Viewer<Handle> {
2020 max_scale : f32 ,
2121 scale_step : f32 ,
2222 handle : Handle ,
23- filter_method : image:: FilterMethod ,
23+ filter_method : FilterMethod ,
24+ content_fit : ContentFit ,
2425}
2526
2627impl < Handle > Viewer < Handle > {
2728 /// Creates a new [`Viewer`] with the given [`State`].
28- pub fn new ( handle : Handle ) -> Self {
29+ pub fn new < T : Into < Handle > > ( handle : T ) -> Self {
2930 Viewer {
30- handle,
31+ handle : handle . into ( ) ,
3132 padding : 0.0 ,
3233 width : Length :: Shrink ,
3334 height : Length :: Shrink ,
3435 min_scale : 0.25 ,
3536 max_scale : 10.0 ,
3637 scale_step : 0.10 ,
37- filter_method : image:: FilterMethod :: default ( ) ,
38+ filter_method : FilterMethod :: default ( ) ,
39+ content_fit : ContentFit :: default ( ) ,
3840 }
3941 }
4042
41- /// Sets the [`image:: FilterMethod`] of the [`Viewer`].
43+ /// Sets the [`FilterMethod`] of the [`Viewer`].
4244 pub fn filter_method ( mut self , filter_method : image:: FilterMethod ) -> Self {
4345 self . filter_method = filter_method;
4446 self
4547 }
4648
49+ /// Sets the [`ContentFit`] of the [`Viewer`].
50+ pub fn content_fit ( mut self , content_fit : ContentFit ) -> Self {
51+ self . content_fit = content_fit;
52+ self
53+ }
54+
4755 /// Sets the padding of the [`Viewer`].
4856 pub fn padding ( mut self , padding : impl Into < Pixels > ) -> Self {
4957 self . padding = padding. into ( ) . 0 ;
@@ -115,36 +123,30 @@ where
115123 renderer : & Renderer ,
116124 limits : & layout:: Limits ,
117125 ) -> layout:: Node {
118- let Size { width, height } = renderer. measure_image ( & self . handle ) ;
119-
120- let mut size = limits. resolve (
121- self . width ,
122- self . height ,
123- Size :: new ( width as f32 , height as f32 ) ,
124- ) ;
125-
126- let expansion_size = if height > width {
127- self . width
128- } else {
129- self . height
126+ // The raw w/h of the underlying image
127+ let image_size = renderer. measure_image ( & self . handle ) ;
128+ let image_size =
129+ Size :: new ( image_size. width as f32 , image_size. height as f32 ) ;
130+
131+ // The size to be available to the widget prior to `Shrink`ing
132+ let raw_size = limits. resolve ( self . width , self . height , image_size) ;
133+
134+ // The uncropped size of the image when fit to the bounds above
135+ let full_size = self . content_fit . fit ( image_size, raw_size) ;
136+
137+ // Shrink the widget to fit the resized image, if requested
138+ let final_size = Size {
139+ width : match self . width {
140+ Length :: Shrink => f32:: min ( raw_size. width , full_size. width ) ,
141+ _ => raw_size. width ,
142+ } ,
143+ height : match self . height {
144+ Length :: Shrink => f32:: min ( raw_size. height , full_size. height ) ,
145+ _ => raw_size. height ,
146+ } ,
130147 } ;
131148
132- // Only calculate viewport sizes if the images are constrained to a limited space.
133- // If they are Fill|Portion let them expand within their allotted space.
134- match expansion_size {
135- Length :: Shrink | Length :: Fixed ( _) => {
136- let aspect_ratio = width as f32 / height as f32 ;
137- let viewport_aspect_ratio = size. width / size. height ;
138- if viewport_aspect_ratio > aspect_ratio {
139- size. width = width as f32 * size. height / height as f32 ;
140- } else {
141- size. height = height as f32 * size. width / width as f32 ;
142- }
143- }
144- Length :: Fill | Length :: FillPortion ( _) => { }
145- }
146-
147- layout:: Node :: new ( size)
149+ layout:: Node :: new ( final_size)
148150 }
149151
150152 fn on_event (
@@ -182,11 +184,12 @@ where
182184 } )
183185 . clamp ( self . min_scale , self . max_scale ) ;
184186
185- let image_size = image_size (
187+ let scaled_size = scaled_image_size (
186188 renderer,
187189 & self . handle ,
188190 state,
189191 bounds. size ( ) ,
192+ self . content_fit ,
190193 ) ;
191194
192195 let factor = state. scale / previous_scale - 1.0 ;
@@ -198,12 +201,12 @@ where
198201 + state. current_offset * factor;
199202
200203 state. current_offset = Vector :: new (
201- if image_size . width > bounds. width {
204+ if scaled_size . width > bounds. width {
202205 state. current_offset . x + adjustment. x
203206 } else {
204207 0.0
205208 } ,
206- if image_size . height > bounds. height {
209+ if scaled_size . height > bounds. height {
207210 state. current_offset . y + adjustment. y
208211 } else {
209212 0.0
@@ -242,32 +245,32 @@ where
242245 let state = tree. state . downcast_mut :: < State > ( ) ;
243246
244247 if let Some ( origin) = state. cursor_grabbed_at {
245- let image_size = image_size (
248+ let scaled_size = scaled_image_size (
246249 renderer,
247250 & self . handle ,
248251 state,
249252 bounds. size ( ) ,
253+ self . content_fit ,
250254 ) ;
251-
252- let hidden_width = ( image_size. width - bounds. width / 2.0 )
255+ let hidden_width = ( scaled_size. width - bounds. width / 2.0 )
253256 . max ( 0.0 )
254257 . round ( ) ;
255258
256- let hidden_height = ( image_size . height
259+ let hidden_height = ( scaled_size . height
257260 - bounds. height / 2.0 )
258261 . max ( 0.0 )
259262 . round ( ) ;
260263
261264 let delta = position - origin;
262265
263- let x = if bounds. width < image_size . width {
266+ let x = if bounds. width < scaled_size . width {
264267 ( state. starting_offset . x - delta. x )
265268 . clamp ( -hidden_width, hidden_width)
266269 } else {
267270 0.0
268271 } ;
269272
270- let y = if bounds. height < image_size . height {
273+ let y = if bounds. height < scaled_size . height {
271274 ( state. starting_offset . y - delta. y )
272275 . clamp ( -hidden_height, hidden_height)
273276 } else {
@@ -319,33 +322,43 @@ where
319322 let state = tree. state . downcast_ref :: < State > ( ) ;
320323 let bounds = layout. bounds ( ) ;
321324
322- let image_size =
323- image_size ( renderer, & self . handle , state, bounds. size ( ) ) ;
325+ let final_size = scaled_image_size (
326+ renderer,
327+ & self . handle ,
328+ state,
329+ bounds. size ( ) ,
330+ self . content_fit ,
331+ ) ;
324332
325333 let translation = {
326- let image_top_left = Vector :: new (
327- bounds. width / 2.0 - image_size. width / 2.0 ,
328- bounds. height / 2.0 - image_size. height / 2.0 ,
329- ) ;
334+ let diff_w = bounds. width - final_size. width ;
335+ let diff_h = bounds. height - final_size. height ;
330336
331- image_top_left - state. offset ( bounds, image_size)
337+ let image_top_left = match self . content_fit {
338+ ContentFit :: None => {
339+ Vector :: new ( diff_w. max ( 0.0 ) / 2.0 , diff_h. max ( 0.0 ) / 2.0 )
340+ }
341+ _ => Vector :: new ( diff_w / 2.0 , diff_h / 2.0 ) ,
342+ } ;
343+
344+ image_top_left - state. offset ( bounds, final_size)
332345 } ;
333346
334- renderer. with_layer ( bounds, |renderer| {
347+ let drawing_bounds = Rectangle :: new ( bounds. position ( ) , final_size) ;
348+
349+ let render = |renderer : & mut Renderer | {
335350 renderer. with_translation ( translation, |renderer| {
336351 renderer. draw_image (
337352 self . handle . clone ( ) ,
338353 self . filter_method ,
339- Rectangle {
340- x : bounds. x ,
341- y : bounds. y ,
342- ..Rectangle :: with_size ( image_size)
343- } ,
354+ drawing_bounds,
344355 Radians ( 0.0 ) ,
345356 1.0 ,
346357 ) ;
347358 } ) ;
348- } ) ;
359+ } ;
360+
361+ renderer. with_layer ( bounds, render) ;
349362 }
350363}
351364
@@ -411,32 +424,23 @@ where
411424/// Returns the bounds of the underlying image, given the bounds of
412425/// the [`Viewer`]. Scaling will be applied and original aspect ratio
413426/// will be respected.
414- pub fn image_size < Renderer > (
427+ pub fn scaled_image_size < Renderer > (
415428 renderer : & Renderer ,
416429 handle : & <Renderer as image:: Renderer >:: Handle ,
417430 state : & State ,
418431 bounds : Size ,
432+ content_fit : ContentFit ,
419433) -> Size
420434where
421435 Renderer : image:: Renderer ,
422436{
423437 let Size { width, height } = renderer. measure_image ( handle) ;
438+ let image_size = Size :: new ( width as f32 , height as f32 ) ;
424439
425- let ( width, height) = {
426- let dimensions = ( width as f32 , height as f32 ) ;
427-
428- let width_ratio = bounds. width / dimensions. 0 ;
429- let height_ratio = bounds. height / dimensions. 1 ;
430-
431- let ratio = width_ratio. min ( height_ratio) ;
432- let scale = state. scale ;
433-
434- if ratio < 1.0 {
435- ( dimensions. 0 * ratio * scale, dimensions. 1 * ratio * scale)
436- } else {
437- ( dimensions. 0 * scale, dimensions. 1 * scale)
438- }
439- } ;
440+ let adjusted_fit = content_fit. fit ( image_size, bounds) ;
440441
441- Size :: new ( width, height)
442+ Size :: new (
443+ adjusted_fit. width * state. scale ,
444+ adjusted_fit. height * state. scale ,
445+ )
442446}
0 commit comments