Skip to content

Commit 368b15f

Browse files
authored
Merge pull request #2330 from Gigas002/viewer_content_fit
Implement content_fit for viewer widget
2 parents 6c1027a + c95e296 commit 368b15f

1 file changed

Lines changed: 80 additions & 76 deletions

File tree

widget/src/image/viewer.rs

Lines changed: 80 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Zoom and pan on an image.
22
use crate::core::event::{self, Event};
3-
use crate::core::image;
3+
use crate::core::image::{self, FilterMethod};
44
use crate::core::layout;
55
use crate::core::mouse;
66
use crate::core::renderer;
77
use crate::core::widget::tree::{self, Tree};
88
use 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

2627
impl<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
420434
where
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

Comments
 (0)