mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 02:39:28 +00:00
Image: add horizontal and vertical alignment
This commit is contained in:
parent
900c0d50a5
commit
ce6c7f5527
11 changed files with 91 additions and 25 deletions
|
@ -297,6 +297,7 @@ An `Image` can be used to represent an image loaded from a file.
|
|||
### Properties
|
||||
|
||||
- **`colorize`** (_in_ _brush_): When set, the image is used as an alpha mask and is drawn in the given color (or with the gradient).
|
||||
- **`horizontal-alignment`** (_in_ _enum [`ImageHorizontalAlignment`](enums.md#imagehorizontalalignment)_): The horizontal alignment of the image within the element.
|
||||
- **`image-fit`** (_in_ _enum [`ImageFit`](enums.md#imagefit)_): Specifies how the source image shall be fit into the image element. (default value: `contain` when the `Image` element is part of a layout, `fill` otherwise)
|
||||
- **`image-rendering`** (_in_ _enum [`ImageRendering`](enums.md#imagerendering)_): Specifies how the source image will be scaled. (default value: `smooth`)
|
||||
- **`rotation-angle`** (_in_ _angle_), **`rotation-origin-x`** (_in_ _length_), **`rotation-origin-y`** (_in_ _length_):
|
||||
|
@ -311,6 +312,7 @@ An `Image` can be used to represent an image loaded from a file.
|
|||
| `source-clip-y` | `0` |
|
||||
| `source-clip-width` | `source.width - source-clip-x` |
|
||||
| `source-clip-height` | `source.height - source-clip-y` |
|
||||
- **`vertical-alignment`** (_in_ _enum [`ImageVerticalAlignment`](enums.md#imageverticalalignment)_): The vertical alignment of the image within the element.
|
||||
- **`width`**, **`height`** (_in_ _length_): The width and height of the image as it appears on the screen.The default values are
|
||||
the sizes provided by the **`source`** image. If the `Image` is **not** in a layout and only **one** of the two sizes are
|
||||
specified, then the other defaults to the specified value scaled according to the aspect ratio of the **`source`** image.
|
||||
|
|
|
@ -1221,6 +1221,7 @@ impl QtItemRenderer<'_> {
|
|||
t.cast(),
|
||||
IntRect::from_size(origin.cast()),
|
||||
scale_factor,
|
||||
Default::default(), // We only care about the size, so alignments don't matter
|
||||
)
|
||||
.size
|
||||
.cast(),
|
||||
|
@ -1257,6 +1258,7 @@ impl QtItemRenderer<'_> {
|
|||
size * scale_factor,
|
||||
source_rect,
|
||||
scale_factor,
|
||||
image.alignment(),
|
||||
);
|
||||
|
||||
let dest_rect = qttypes::QRectF {
|
||||
|
|
|
@ -233,6 +233,30 @@ macro_rules! for_each_enums {
|
|||
Preserve,
|
||||
}
|
||||
|
||||
/// This enum specifies the horizontal alignment of the source image.
|
||||
enum ImageHorizontalAlignment {
|
||||
/// Aligns the source image at the center of the [`Image`](elements.md#image) element.
|
||||
Center,
|
||||
/// Aligns the source image at the left of the [`Image`](elements.md#image) element.
|
||||
Left,
|
||||
/// Aligns the source image at the right of the [`Image`](elements.md#image) element.
|
||||
Right,
|
||||
/// Aligns the source image at the start of the [`Image`](elements.md#image) element. This could be left or right depending on the language.
|
||||
Start,
|
||||
/// Aligns the source image at the end of the [`Image`](elements.md#image) element. This could be left or right depending on the language.
|
||||
End,
|
||||
}
|
||||
|
||||
/// This enum specifies the vertical alignment of the source image.
|
||||
enum ImageVerticalAlignment {
|
||||
/// Aligns the source image at the center of the [`Image`](elements.md#image) element.
|
||||
Center,
|
||||
/// Aligns the source image at the top of the [`Image`](elements.md#image) element.
|
||||
Top,
|
||||
/// Aligns the source image at the bottom of the [`Image`](elements.md#image) element.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// This enum specifies how the source image will be scaled.
|
||||
enum ImageRendering {
|
||||
/// The image is scaled with a linear interpolation algorithm.
|
||||
|
|
|
@ -60,6 +60,9 @@ export component ClippedImage inherits ImageItem {
|
|||
in property <int> source-clip-width;
|
||||
in property <int> source-clip-height;
|
||||
//-default_size_binding:implicit_size
|
||||
|
||||
in property <ImageHorizontalAlignment> horizontal-alignment;
|
||||
in property <ImageVerticalAlignment> vertical-alignment;
|
||||
}
|
||||
|
||||
export { ClippedImage as Image }
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::slice::Slice;
|
|||
use crate::{SharedString, SharedVector};
|
||||
|
||||
use super::{IntRect, IntSize};
|
||||
use crate::items::ImageFit;
|
||||
use crate::items::{ImageFit, ImageHorizontalAlignment, ImageVerticalAlignment};
|
||||
|
||||
#[cfg(feature = "image-decoders")]
|
||||
pub mod cache;
|
||||
|
@ -827,6 +827,7 @@ pub fn fit(
|
|||
target: euclid::Size2D<f32, PhysicalPx>,
|
||||
mut source_rect: IntRect,
|
||||
scale_factor: ScaleFactor,
|
||||
alignment: (ImageHorizontalAlignment, ImageVerticalAlignment),
|
||||
) -> FitResult {
|
||||
let o = source_rect.size.cast::<f32>();
|
||||
let mut offset = Default::default();
|
||||
|
@ -848,19 +849,37 @@ pub fn fit(
|
|||
let mut size = euclid::Size2D::from_untyped(o * ratio);
|
||||
if (o.width as f32) > target.width / ratio {
|
||||
let diff = (o.width as f32 - target.width / ratio) as i32;
|
||||
source_rect.origin.x += diff / 2;
|
||||
source_rect.size.width -= diff;
|
||||
source_rect.origin.x += match alignment.0 {
|
||||
ImageHorizontalAlignment::Center => diff / 2,
|
||||
ImageHorizontalAlignment::Left | ImageHorizontalAlignment::Start => 0,
|
||||
ImageHorizontalAlignment::Right | ImageHorizontalAlignment::End => diff,
|
||||
};
|
||||
size.width = target.width;
|
||||
} else if (o.width as f32) < target.width / ratio {
|
||||
offset.x = (target.width - o.width as f32 * ratio) / 2.;
|
||||
offset.x = match alignment.0 {
|
||||
ImageHorizontalAlignment::Center => (target.width - o.width as f32 * ratio) / 2.,
|
||||
ImageHorizontalAlignment::Left | ImageHorizontalAlignment::Start => 0.,
|
||||
ImageHorizontalAlignment::Right | ImageHorizontalAlignment::End => {
|
||||
target.width - o.width as f32 * ratio
|
||||
}
|
||||
};
|
||||
}
|
||||
if (o.height as f32) > target.height / ratio {
|
||||
let diff = (o.height as f32 - target.height / ratio) as i32;
|
||||
source_rect.origin.y += diff / 2;
|
||||
source_rect.size.height -= diff;
|
||||
source_rect.origin.y += match alignment.1 {
|
||||
ImageVerticalAlignment::Center => diff / 2,
|
||||
ImageVerticalAlignment::Top => 0,
|
||||
ImageVerticalAlignment::Bottom => diff,
|
||||
};
|
||||
size.height = target.height;
|
||||
} else if (o.height as f32) < target.height / ratio {
|
||||
offset.y = (target.height - o.height as f32 * ratio) / 2.;
|
||||
offset.y = match alignment.1 {
|
||||
ImageVerticalAlignment::Center => (target.height - o.height as f32 * ratio) / 2.,
|
||||
ImageVerticalAlignment::Top => 0.,
|
||||
ImageVerticalAlignment::Bottom => target.height - o.height as f32 * ratio,
|
||||
};
|
||||
}
|
||||
FitResult {
|
||||
clip_rect: source_rect,
|
||||
|
|
|
@ -296,6 +296,7 @@ pub trait RenderImage {
|
|||
fn image_fit(self: Pin<&Self>) -> ImageFit;
|
||||
fn rendering(self: Pin<&Self>) -> ImageRendering;
|
||||
fn colorize(self: Pin<&Self>) -> Brush;
|
||||
fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment);
|
||||
}
|
||||
|
||||
/// Trait used to render each items.
|
||||
|
|
|
@ -7,7 +7,10 @@ This module contains the builtin image related items.
|
|||
When adding an item or a property, it needs to be kept in sync with different place.
|
||||
Lookup the [`crate::items`] module documentation.
|
||||
*/
|
||||
use super::{ImageFit, ImageRendering, Item, ItemConsts, ItemRc, RenderingResult};
|
||||
use super::{
|
||||
ImageFit, ImageHorizontalAlignment, ImageRendering, ImageVerticalAlignment, Item, ItemConsts,
|
||||
ItemRc, RenderingResult,
|
||||
};
|
||||
use crate::input::{
|
||||
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
|
||||
KeyEventResult, MouseEvent,
|
||||
|
@ -131,6 +134,10 @@ impl RenderImage for ImageItem {
|
|||
fn colorize(self: Pin<&Self>) -> Brush {
|
||||
self.colorize()
|
||||
}
|
||||
|
||||
fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment) {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemConsts for ImageItem {
|
||||
|
@ -155,6 +162,10 @@ pub struct ClippedImage {
|
|||
pub source_clip_y: Property<i32>,
|
||||
pub source_clip_width: Property<i32>,
|
||||
pub source_clip_height: Property<i32>,
|
||||
|
||||
pub horizontal_alignment: Property<ImageHorizontalAlignment>,
|
||||
pub vertical_alignment: Property<ImageVerticalAlignment>,
|
||||
|
||||
pub cached_rendering_data: CachedRenderingData,
|
||||
}
|
||||
|
||||
|
@ -259,6 +270,10 @@ impl RenderImage for ClippedImage {
|
|||
fn colorize(self: Pin<&Self>) -> Brush {
|
||||
self.colorize()
|
||||
}
|
||||
|
||||
fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment) {
|
||||
(self.horizontal_alignment(), self.vertical_alignment())
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemConsts for ClippedImage {
|
||||
|
|
|
@ -13,11 +13,11 @@ mod fonts;
|
|||
use self::fonts::GlyphRenderer;
|
||||
use crate::api::Window;
|
||||
use crate::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector};
|
||||
use crate::graphics::{BorderRadius, IntRect, PixelFormat, SharedImageBuffer, SharedPixelBuffer};
|
||||
use crate::graphics::{BorderRadius, PixelFormat, SharedImageBuffer, SharedPixelBuffer};
|
||||
use crate::item_rendering::{
|
||||
CachedRenderingData, ItemRenderer, RenderBorderRectangle, RenderImage,
|
||||
};
|
||||
use crate::items::{ImageFit, ItemRc, TextOverflow};
|
||||
use crate::items::{ItemRc, TextOverflow};
|
||||
use crate::lengths::{
|
||||
LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
|
||||
PhysicalPx, PointLengths, RectLengths, ScaleFactor, SizeLengths,
|
||||
|
@ -1352,24 +1352,18 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
|
|||
|
||||
fn draw_image_impl(
|
||||
&mut self,
|
||||
geom: LogicalRect,
|
||||
source: &crate::graphics::Image,
|
||||
source_rect: IntRect,
|
||||
image_fit: ImageFit,
|
||||
colorize: Color,
|
||||
) {
|
||||
let global_alpha_u16 = (self.current_state.alpha * 255.) as u16;
|
||||
let image_inner: &ImageInner = source.into();
|
||||
let phys_size = geom.size_length().cast() * self.scale_factor;
|
||||
|
||||
let crate::graphics::FitResult {
|
||||
crate::graphics::FitResult {
|
||||
clip_rect: source_rect,
|
||||
source_to_target_x,
|
||||
source_to_target_y,
|
||||
size: fit_size,
|
||||
offset: image_fit_offset,
|
||||
} = crate::graphics::fit(image_fit, phys_size, source_rect, self.scale_factor);
|
||||
|
||||
}: crate::graphics::FitResult,
|
||||
colorize: Color,
|
||||
) {
|
||||
let global_alpha_u16 = (self.current_state.alpha * 255.) as u16;
|
||||
let image_inner: &ImageInner = source.into();
|
||||
let offset = (self.current_state.offset.cast() * self.scale_factor
|
||||
+ image_fit_offset.to_vector())
|
||||
.cast();
|
||||
|
@ -1886,13 +1880,15 @@ impl<'a, T: ProcessScene> crate::item_rendering::ItemRenderer for SceneBuilder<'
|
|||
.source_clip()
|
||||
.unwrap_or_else(|| euclid::Rect::new(Default::default(), source.size().cast()));
|
||||
|
||||
self.draw_image_impl(
|
||||
geom,
|
||||
&source,
|
||||
source_clip,
|
||||
let phys_size = geom.size_length().cast() * self.scale_factor;
|
||||
let fit = crate::graphics::fit(
|
||||
image.image_fit(),
|
||||
image.colorize().color(),
|
||||
phys_size,
|
||||
source_clip,
|
||||
self.scale_factor,
|
||||
image.alignment(),
|
||||
);
|
||||
self.draw_image_impl(&source, fit, image.colorize().color());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1300,6 +1300,7 @@ impl<'a> GLItemRenderer<'a> {
|
|||
t,
|
||||
IntRect::from_size(image_size.cast()),
|
||||
self.scale_factor,
|
||||
Default::default(), // We only care about the size, so alignments don't matter
|
||||
)
|
||||
.size
|
||||
.cast(),
|
||||
|
@ -1367,6 +1368,7 @@ impl<'a> GLItemRenderer<'a> {
|
|||
size * self.scale_factor,
|
||||
source_clip_rect,
|
||||
self.scale_factor,
|
||||
item.alignment(),
|
||||
);
|
||||
|
||||
let fill_paint = femtovg::Paint::image(
|
||||
|
|
|
@ -58,6 +58,7 @@ pub(crate) fn as_skia_image(
|
|||
target_size_fn() * scale_factor,
|
||||
IntRect::from_size(svg.size().cast()),
|
||||
scale_factor,
|
||||
Default::default(), // We only care about the size, so alignments don't matter
|
||||
)
|
||||
.size;
|
||||
let pixels = match svg.render(Some(target_size.cast())).ok()? {
|
||||
|
|
|
@ -188,6 +188,7 @@ impl<'a> SkiaItemRenderer<'a> {
|
|||
item.source_clip()
|
||||
.unwrap_or_else(|| euclid::rect(0, 0, skia_image.width(), skia_image.height())),
|
||||
self.scale_factor,
|
||||
item.alignment(),
|
||||
);
|
||||
|
||||
let dst = to_skia_rect(&PhysicalRect::new(fit.offset, fit.size));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue