Port the FemtoVG renderer to use different types for logical and physical lengths (#1696)

This commit is contained in:
Simon Hausmann 2022-09-30 09:31:11 +02:00 committed by GitHub
parent 7f6a6aa57b
commit f66a2a5775
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 307 additions and 212 deletions

View file

@ -3,15 +3,21 @@
use std::{cell::RefCell, collections::BTreeMap};
use i_slint_core::{items::ItemRc, Color};
use i_slint_core::graphics::euclid;
use i_slint_core::items::ItemRc;
use i_slint_core::lengths::LogicalLength;
use i_slint_core::{
lengths::{PhysicalPx, ScaleFactor},
Color,
};
#[derive(Clone, PartialEq, Debug, Default)]
pub struct BoxShadowOptions {
pub width: f32,
pub height: f32,
pub width: euclid::Length<f32, PhysicalPx>,
pub height: euclid::Length<f32, PhysicalPx>,
pub color: Color,
pub blur: f32,
pub radius: f32,
pub blur: euclid::Length<f32, PhysicalPx>,
pub radius: euclid::Length<f32, PhysicalPx>,
}
impl Eq for BoxShadowOptions {}
@ -40,18 +46,18 @@ impl PartialOrd for BoxShadowOptions {
impl BoxShadowOptions {
fn new(
box_shadow: std::pin::Pin<&i_slint_core::items::BoxShadow>,
scale_factor: f32,
scale_factor: ScaleFactor,
) -> Option<Self> {
let color = box_shadow.color();
if color.alpha() == 0 {
return None;
}
Some(Self {
width: box_shadow.width() * scale_factor,
height: box_shadow.height() * scale_factor,
width: LogicalLength::new(box_shadow.width()) * scale_factor,
height: LogicalLength::new(box_shadow.height()) * scale_factor,
color,
blur: box_shadow.blur() * scale_factor, // This effectively becomes the blur radius, so scale to physical pixels
radius: box_shadow.border_radius() * scale_factor,
blur: LogicalLength::new(box_shadow.blur()) * scale_factor, // This effectively becomes the blur radius, so scale to physical pixels
radius: LogicalLength::new(box_shadow.border_radius()) * scale_factor,
})
}
}
@ -70,7 +76,7 @@ impl<ImageType: Clone> BoxShadowCache<ImageType> {
item_rc: &ItemRc,
item_cache: &i_slint_core::item_rendering::ItemCache<Option<ImageType>>,
box_shadow: std::pin::Pin<&i_slint_core::items::BoxShadow>,
scale_factor: f32,
scale_factor: ScaleFactor,
shadow_render_fn: impl FnOnce(&BoxShadowOptions) -> ImageType,
) -> Option<ImageType> {
item_cache.get_or_update_cache_entry(item_rc, || {

View file

@ -11,12 +11,20 @@ use i_slint_core::api::{
use i_slint_core::graphics::{
euclid, rendering_metrics_collector::RenderingMetricsCollector, Point, Rect, Size,
};
use i_slint_core::lengths::{
LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
};
use i_slint_core::renderer::Renderer;
use i_slint_core::window::{WindowAdapter, WindowInner};
use i_slint_core::Coord;
use crate::WindowSystemName;
type PhysicalLength = euclid::Length<f32, PhysicalPx>;
type PhysicalRect = euclid::Rect<f32, PhysicalPx>;
type PhysicalSize = euclid::Size2D<f32, PhysicalPx>;
type PhysicalPoint = euclid::Point2D<f32, PhysicalPx>;
use self::itemrenderer::CanvasRc;
mod fonts;
@ -215,7 +223,13 @@ impl Renderer for FemtoVGRenderer {
max_width: Option<Coord>,
scale_factor: f32,
) -> Size {
crate::renderer::femtovg::fonts::text_size(&font_request, scale_factor, text, max_width)
crate::renderer::femtovg::fonts::text_size(
&font_request,
ScaleFactor::new(scale_factor),
text,
max_width.map(|w| LogicalLength::new(w)),
)
.to_untyped()
}
fn text_input_byte_offset_for_position(
@ -230,15 +244,15 @@ impl Renderer for FemtoVGRenderer {
let window = WindowInner::from_pub(window_adapter.window());
let scale_factor = window.scale_factor();
let pos = pos * scale_factor;
let scale_factor = ScaleFactor::new(window.scale_factor());
let pos = LogicalPoint::from_untyped(pos) * scale_factor;
let text = text_input.text();
let mut result = text.len();
let width = text_input.width() * scale_factor;
let height = text_input.height() * scale_factor;
if width <= 0. || height <= 0. || pos.y < 0. {
let width = LogicalLength::new(text_input.width()) * scale_factor;
let height = LogicalLength::new(text_input.height()) * scale_factor;
if width.get() <= 0. || height.get() <= 0. || pos.y < 0. {
return 0;
}
@ -260,14 +274,17 @@ impl Renderer for FemtoVGRenderer {
text.as_str()
};
let paint = font.init_paint(text_input.letter_spacing() * scale_factor, Default::default());
let paint = font.init_paint(
LogicalLength::new(text_input.letter_spacing()) * scale_factor,
Default::default(),
);
let text_context = crate::renderer::femtovg::fonts::FONT_CACHE
.with(|cache| cache.borrow().text_context.clone());
let font_height = text_context.measure_font(paint).unwrap().height();
crate::renderer::femtovg::fonts::layout_text_lines(
actual_text,
&font,
Size::new(width, height),
PhysicalSize::from_lengths(width, height),
(text_input.horizontal_alignment(), text_input.vertical_alignment()),
text_input.wrap(),
i_slint_core::items::TextOverflow::Clip,
@ -310,17 +327,17 @@ impl Renderer for FemtoVGRenderer {
let window = WindowInner::from_pub(window_adapter.window());
let text = text_input.text();
let scale_factor = window.scale_factor();
let scale_factor = ScaleFactor::new(window.scale_factor());
let font_size =
text_input.font_request(&window_adapter).pixel_size.unwrap_or(fonts::DEFAULT_FONT_SIZE);
let mut result = Point::default();
let mut result = PhysicalPoint::default();
let width = text_input.width() * scale_factor;
let height = text_input.height() * scale_factor;
if width <= 0. || height <= 0. {
return Rect::new(result, Size::new(1.0, font_size));
let width = LogicalLength::new(text_input.width()) * scale_factor;
let height = LogicalLength::new(text_input.height()) * scale_factor;
if width.get() <= 0. || height.get() <= 0. {
return Rect::new(Point::default(), Size::new(1.0, font_size));
}
let font = crate::renderer::femtovg::fonts::FONT_CACHE.with(|cache| {
@ -331,11 +348,14 @@ impl Renderer for FemtoVGRenderer {
)
});
let paint = font.init_paint(text_input.letter_spacing() * scale_factor, Default::default());
let paint = font.init_paint(
LogicalLength::new(text_input.letter_spacing()) * scale_factor,
Default::default(),
);
fonts::layout_text_lines(
text.as_str(),
&font,
Size::new(width, height),
PhysicalSize::from_lengths(width, height),
(text_input.horizontal_alignment(), text_input.vertical_alignment()),
text_input.wrap(),
i_slint_core::items::TextOverflow::Clip,
@ -345,18 +365,19 @@ impl Renderer for FemtoVGRenderer {
if (start..=(start + line_text.len())).contains(&byte_offset) {
for glyph in &metrics.glyphs {
if glyph.byte_index == (byte_offset - start) {
result = line_pos + euclid::vec2(glyph.x, 0.0);
result = line_pos + PhysicalPoint::new(glyph.x, 0.0).to_vector();
return;
}
}
if let Some(last) = metrics.glyphs.last() {
result = line_pos + euclid::vec2(last.x + last.advance_x, last.y);
result = line_pos
+ PhysicalPoint::new(last.x + last.advance_x, last.y).to_vector();
}
}
},
);
Rect::new(result / scale_factor, Size::new(1.0, font_size))
LogicalRect::new(result / scale_factor, LogicalSize::new(1.0, font_size)).to_untyped()
}
fn register_font_from_memory(

View file

@ -5,12 +5,15 @@
use femtovg::TextContext;
use i_slint_core::graphics::euclid;
use i_slint_core::graphics::{FontRequest, Point, Size};
use i_slint_core::graphics::FontRequest;
use i_slint_core::items::{TextHorizontalAlignment, TextOverflow, TextVerticalAlignment, TextWrap};
use i_slint_core::lengths::{LogicalLength, LogicalSize, ScaleFactor, SizeLengths};
use i_slint_core::{SharedString, SharedVector};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use super::{PhysicalLength, PhysicalPoint, PhysicalSize};
pub const DEFAULT_FONT_SIZE: f32 = 12.;
pub const DEFAULT_FONT_WEIGHT: i32 = 400; // CSS normal
@ -77,15 +80,24 @@ pub struct Font {
}
impl Font {
pub fn init_paint(&self, letter_spacing: f32, mut paint: femtovg::Paint) -> femtovg::Paint {
pub fn init_paint(
&self,
letter_spacing: PhysicalLength,
mut paint: femtovg::Paint,
) -> femtovg::Paint {
paint.set_font(&self.fonts);
paint.set_font_size(self.pixel_size);
paint.set_text_baseline(femtovg::Baseline::Top);
paint.set_letter_spacing(letter_spacing);
paint.set_letter_spacing(letter_spacing.get());
paint
}
pub fn text_size(&self, letter_spacing: f32, text: &str, max_width: Option<f32>) -> Size {
pub fn text_size(
&self,
letter_spacing: PhysicalLength,
text: &str,
max_width: Option<PhysicalLength>,
) -> PhysicalSize {
let paint = self.init_paint(letter_spacing, femtovg::Paint::default());
let font_metrics = self.text_context.measure_font(paint).unwrap();
let mut lines = 0;
@ -93,7 +105,8 @@ impl Font {
let mut start = 0;
if let Some(max_width) = max_width {
while start < text.len() {
let index = self.text_context.break_text(max_width, &text[start..], paint).unwrap();
let index =
self.text_context.break_text(max_width.get(), &text[start..], paint).unwrap();
if index == 0 {
break;
}
@ -117,14 +130,15 @@ impl Font {
pub(crate) fn text_size(
font_request: &i_slint_core::graphics::FontRequest,
scale_factor: f32,
scale_factor: ScaleFactor,
text: &str,
max_width: Option<f32>,
) -> Size {
max_width: Option<LogicalLength>,
) -> LogicalSize {
let font =
FONT_CACHE.with(|cache| cache.borrow_mut().font(font_request.clone(), scale_factor, text));
let letter_spacing = font_request.letter_spacing.unwrap_or_default();
font.text_size(letter_spacing, text, max_width.map(|x| x * scale_factor)) / scale_factor
let letter_spacing = LogicalLength::new(font_request.letter_spacing.unwrap_or_default());
font.text_size(letter_spacing * scale_factor, text, max_width.map(|x| x * scale_factor))
/ scale_factor
}
#[derive(Copy, Clone)]
@ -314,10 +328,11 @@ impl FontCache {
pub fn font(
&mut self,
mut request: FontRequest,
scale_factor: f32,
scale_factor: ScaleFactor,
reference_text: &str,
) -> Font {
request.pixel_size = Some(request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE) * scale_factor);
request.pixel_size =
Some(request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE) * scale_factor.get());
request.weight = request.weight.or(Some(DEFAULT_FONT_WEIGHT));
let primary_font = self.load_single_font(&request);
@ -630,20 +645,23 @@ impl FontCache {
pub(crate) fn layout_text_lines(
string: &str,
font: &Font,
Size { width: max_width, height: max_height, .. }: Size,
max_size: PhysicalSize,
(horizontal_alignment, vertical_alignment): (TextHorizontalAlignment, TextVerticalAlignment),
wrap: TextWrap,
overflow: TextOverflow,
single_line: bool,
paint: femtovg::Paint,
mut layout_line: impl FnMut(&str, Point, usize, &femtovg::TextMetrics),
) -> f32 {
mut layout_line: impl FnMut(&str, PhysicalPoint, usize, &femtovg::TextMetrics),
) -> PhysicalLength {
let wrap = wrap == TextWrap::WordWrap;
let elide = overflow == TextOverflow::Elide;
let max_width = max_size.width_length();
let max_height = max_size.height_length();
let text_context = FONT_CACHE.with(|cache| cache.borrow().text_context.clone());
let font_metrics = text_context.measure_font(paint).unwrap();
let font_height = font_metrics.height();
let font_height = PhysicalLength::new(font_metrics.height());
let text_height = || {
if single_line {
@ -651,38 +669,38 @@ pub(crate) fn layout_text_lines(
} else {
// Note: this is kind of doing twice the layout because text_size also does it
font.text_size(
paint.letter_spacing(),
PhysicalLength::new(paint.letter_spacing()),
string,
if wrap { Some(max_width) } else { None },
)
.height
.height_length()
}
};
let mut process_line = |text: &str,
y: f32,
start: usize,
line_metrics: &femtovg::TextMetrics| {
let x = match horizontal_alignment {
TextHorizontalAlignment::Left => 0.,
TextHorizontalAlignment::Center => {
max_width / 2. - f32::min(max_width, line_metrics.width()) / 2.
}
TextHorizontalAlignment::Right => max_width - f32::min(max_width, line_metrics.width()),
let mut process_line =
|text: &str, y: PhysicalLength, start: usize, line_metrics: &femtovg::TextMetrics| {
let x = match horizontal_alignment {
TextHorizontalAlignment::Left => PhysicalLength::default(),
TextHorizontalAlignment::Center => {
max_width / 2. - max_width.min(PhysicalLength::new(line_metrics.width())) / 2.
}
TextHorizontalAlignment::Right => {
max_width - max_width.min(PhysicalLength::new(line_metrics.width()))
}
};
layout_line(text, PhysicalPoint::from_lengths(x, y), start, line_metrics);
};
layout_line(text, Point::new(x, y), start, line_metrics);
};
let baseline_y = match vertical_alignment {
TextVerticalAlignment::Top => 0.,
TextVerticalAlignment::Top => PhysicalLength::default(),
TextVerticalAlignment::Center => max_height / 2. - text_height() / 2.,
TextVerticalAlignment::Bottom => max_height - text_height(),
};
let mut y = baseline_y;
let mut start = 0;
'lines: while start < string.len() && y + font_height <= max_height {
if wrap && (!elide || y + 2. * font_height <= max_height) {
let index = text_context.break_text(max_width, &string[start..], paint).unwrap();
if wrap && (!elide || y + font_height * 2. <= max_height) {
let index = text_context.break_text(max_width.get(), &string[start..], paint).unwrap();
if index == 0 {
// FIXME the word is too big to be shown, but we should still break, ideally
break;
@ -702,18 +720,20 @@ pub(crate) fn layout_text_lines(
let line = &string[start..index];
let text_metrics = text_context.measure_text(0., 0., line, paint).unwrap();
let elide_last_line =
elide && index < string.len() && y + 2. * font_height > max_height;
if text_metrics.width() > max_width || elide_last_line {
elide && index < string.len() && y + font_height * 2. > max_height;
if text_metrics.width() > max_width.get() || elide_last_line {
let w = max_width
- if elide {
text_context.measure_text(0., 0., "", paint).unwrap().width()
PhysicalLength::new(
text_context.measure_text(0., 0., "", paint).unwrap().width(),
)
} else {
0.
PhysicalLength::default()
};
let mut current_x = 0.;
for glyph in &text_metrics.glyphs {
current_x += glyph.advance_x;
if current_x >= w {
if current_x >= w.get() {
let txt = &line[..glyph.byte_index];
if elide {
let elided = format!("{}", txt);

View file

@ -8,18 +8,22 @@ use std::rc::Rc;
use euclid::approxeq::ApproxEq;
use i_slint_core::graphics::euclid;
use i_slint_core::graphics::rendering_metrics_collector::RenderingMetrics;
use i_slint_core::graphics::{Image, IntRect, IntSize, Point, Rect, Size};
use i_slint_core::graphics::{Image, IntRect, Point, Rect, Size};
use i_slint_core::item_rendering::{ItemCache, ItemRenderer};
use i_slint_core::items::{
self, Clip, FillRule, ImageFit, ImageRendering, InputType, Item, ItemRc, Layer, Opacity,
self, Clip, FillRule, ImageFit, ImageRendering, InputType, ItemRc, Layer, Opacity,
RenderingResult,
};
use i_slint_core::lengths::{
LogicalItemGeometry, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths,
RectLengths, ScaleFactor,
};
use i_slint_core::window::WindowInner;
use i_slint_core::{Brush, Color, ImageInner, Property, SharedString};
use super::fonts;
use super::images::{Texture, TextureCacheKey};
use super::PASSWORD_CHARACTER;
use super::{fonts, PhysicalLength, PhysicalPoint, PhysicalRect};
use super::{PhysicalSize, PASSWORD_CHARACTER};
use super::super::boxshadowcache::BoxShadowCache;
@ -57,7 +61,7 @@ const KAPPA90: f32 = 0.55228;
#[derive(Clone)]
struct State {
scissor: Rect,
scissor: LogicalRect,
global_alpha: f32,
current_render_target: femtovg::RenderTarget,
}
@ -72,18 +76,19 @@ pub struct GLItemRenderer<'a> {
// `set_render_target` commands with image ids that have been deleted.
layer_images_to_delete_after_flush: RefCell<Vec<Rc<super::images::Texture>>>,
window: &'a i_slint_core::api::Window,
scale_factor: f32,
scale_factor: ScaleFactor,
/// track the state manually since femtovg don't have accessor for its state
state: Vec<State>,
metrics: RenderingMetrics,
}
fn rect_with_radius_to_path(rect: Rect, border_radius: f32) -> femtovg::Path {
fn rect_with_radius_to_path(rect: PhysicalRect, border_radius: PhysicalLength) -> femtovg::Path {
let mut path = femtovg::Path::new();
let x = rect.origin.x;
let y = rect.origin.y;
let width = rect.size.width;
let height = rect.size.height;
let border_radius = border_radius.get();
// If we're drawing a circle, use directly connected bezier curves instead of
// ones with intermediate LineTo verbs, as `rounded_rect` creates, to avoid
// rendering artifacts due to those edges.
@ -95,23 +100,26 @@ fn rect_with_radius_to_path(rect: Rect, border_radius: f32) -> femtovg::Path {
path
}
fn rect_to_path(r: Rect) -> femtovg::Path {
rect_with_radius_to_path(r, 0.)
fn rect_to_path(r: PhysicalRect) -> femtovg::Path {
rect_with_radius_to_path(r, PhysicalLength::default())
}
fn adjust_rect_and_border_for_inner_drawing(rect: &mut Rect, border_width: &mut f32) {
fn adjust_rect_and_border_for_inner_drawing(
rect: &mut PhysicalRect,
border_width: &mut PhysicalLength,
) {
// If the border width exceeds the width, just fill the rectangle.
*border_width = border_width.min((rect.size.width as f32) / 2.);
*border_width = border_width.min(rect.width_length() / 2.);
// adjust the size so that the border is drawn within the geometry
rect.origin.x += *border_width / 2.;
rect.origin.y += *border_width / 2.;
rect.size.width -= *border_width;
rect.size.height -= *border_width;
let half_border_size = PhysicalSize::from_lengths(*border_width / 2., *border_width / 2.);
rect.origin += half_border_size;
rect.size -= half_border_size;
}
fn item_rect<Item: items::Item>(item: Pin<&Item>, scale_factor: f32) -> Rect {
let geometry = item.geometry();
euclid::rect(0., 0., geometry.width() * scale_factor, geometry.height() * scale_factor)
fn item_rect<Item: items::Item>(item: Pin<&Item>, scale_factor: ScaleFactor) -> PhysicalRect {
let geometry = LogicalRect::from_untyped(&item.geometry());
PhysicalRect::new(PhysicalPoint::default(), geometry.size * scale_factor)
}
fn path_bounding_box(canvas: &CanvasRc, path: &mut femtovg::Path) -> euclid::default::Box2D<f32> {
@ -131,10 +139,10 @@ fn path_bounding_box(canvas: &CanvasRc, path: &mut femtovg::Path) -> euclid::def
// Return a femtovg::Path (in physical pixels) that represents the clip_rect, radius and border_width (all logical!)
fn clip_path_for_rect_alike_item(
mut clip_rect: Rect,
mut radius: f32,
mut border_width: f32,
scale_factor: f32,
clip_rect: LogicalRect,
mut radius: LogicalLength,
mut border_width: LogicalLength,
scale_factor: ScaleFactor,
) -> femtovg::Path {
// Femtovg renders evenly 50% inside and 50% outside of the border width. The
// adjust_rect_and_border_for_inner_drawing adjusts the rect so that for drawing it
@ -144,9 +152,9 @@ fn clip_path_for_rect_alike_item(
border_width *= 2.;
// Convert from logical to physical pixels
border_width *= scale_factor;
radius *= scale_factor;
clip_rect *= scale_factor;
let mut border_width = border_width * scale_factor;
let radius = radius * scale_factor;
let mut clip_rect = clip_rect * scale_factor;
adjust_rect_and_border_for_inner_drawing(&mut clip_rect, &mut border_width);
@ -177,19 +185,22 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
return;
}
let mut border_width = rect.border_width() * self.scale_factor;
let mut border_width = LogicalLength::new(rect.border_width()) * self.scale_factor;
// In CSS the border is entirely towards the inside of the boundary
// geometry, while in femtovg the line with for a stroke is 50% in-
// and 50% outwards. We choose the CSS model, so the inner rectangle
// is adjusted accordingly.
adjust_rect_and_border_for_inner_drawing(&mut geometry, &mut border_width);
let mut path = rect_with_radius_to_path(geometry, rect.border_radius() * self.scale_factor);
let mut path = rect_with_radius_to_path(
geometry,
LogicalLength::new(rect.border_radius()) * self.scale_factor,
);
let fill_paint = self.brush_to_paint(rect.background(), &mut path);
let border_paint = self.brush_to_paint(rect.border_color(), &mut path).map(|mut paint| {
paint.set_line_width(border_width);
paint.set_line_width(border_width.get());
paint
});
@ -234,10 +245,10 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
}
fn draw_text(&mut self, text: Pin<&items::Text>, _: &ItemRc) {
let max_width = text.width() * self.scale_factor;
let max_height = text.height() * self.scale_factor;
let max_width = LogicalLength::new(text.width()) * self.scale_factor;
let max_height = LogicalLength::new(text.height()) * self.scale_factor;
if max_width <= 0. || max_height <= 0. {
if max_width.get() <= 0. || max_height.get() <= 0. {
return;
}
@ -254,7 +265,8 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
let paint = match self
.brush_to_paint(text.color(), &mut rect_to_path(item_rect(text, self.scale_factor)))
{
Some(paint) => font.init_paint(text.letter_spacing() * self.scale_factor, paint),
Some(paint) => font
.init_paint(LogicalLength::new(text.letter_spacing()) * self.scale_factor, paint),
None => return,
};
@ -262,7 +274,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
fonts::layout_text_lines(
string,
&font,
Size::new(max_width, max_height),
PhysicalSize::from_lengths(max_width, max_height),
(text.horizontal_alignment(), text.vertical_alignment()),
text.wrap(),
text.overflow(),
@ -275,9 +287,9 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
}
fn draw_text_input(&mut self, text_input: Pin<&items::TextInput>, _: &ItemRc) {
let width = text_input.width() * self.scale_factor;
let height = text_input.height() * self.scale_factor;
if width <= 0. || height <= 0. {
let width = LogicalLength::new(text_input.width()) * self.scale_factor;
let height = LogicalLength::new(text_input.height()) * self.scale_factor;
if width.get() <= 0. || height.get() <= 0. {
return;
}
@ -293,7 +305,10 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
text_input.color(),
&mut rect_to_path(item_rect(text_input, self.scale_factor)),
) {
Some(paint) => font.init_paint(text_input.letter_spacing() * self.scale_factor, paint),
Some(paint) => font.init_paint(
LogicalLength::new(text_input.letter_spacing()) * self.scale_factor,
paint,
),
None => return,
};
@ -305,7 +320,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
&& !text_input.read_only();
let mut cursor_pos = cursor_pos as usize;
let mut canvas = self.canvas.borrow_mut();
let font_height = canvas.measure_font(paint).unwrap().height();
let font_height = PhysicalLength::new(canvas.measure_font(paint).unwrap().height());
let mut text = text_input.text();
if let InputType::Password = text_input.input_type() {
@ -315,12 +330,12 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
text = SharedString::from(PASSWORD_CHARACTER.repeat(text.chars().count()));
};
let mut cursor_point: Option<Point> = None;
let mut cursor_point: Option<PhysicalPoint> = None;
let next_y = fonts::layout_text_lines(
text.as_str(),
&font,
Size::new(width, height),
PhysicalSize::from_lengths(width, height),
(text_input.horizontal_alignment(), text_input.vertical_alignment()),
text_input.wrap(),
items::TextOverflow::Clip,
@ -333,9 +348,9 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
|| range.contains(&max_select)
|| (min_select..max_select).contains(&start))
{
let mut selection_start_x = 0.;
let mut selection_end_x = 0.;
let mut after_selection_x = 0.;
let mut selection_start_x = PhysicalLength::default();
let mut selection_end_x = PhysicalLength::default();
let mut after_selection_x = PhysicalLength::default();
// Determine the first and last (inclusive) glyph of the selection. The anchor
// will always be at the start of a grapheme boundary, so there's at ShapedGlyph
// that has a matching byte index. For the selection end we have to look for the
@ -346,20 +361,27 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
// steps with clip to draw each part of the ligature in a different color
for glyph in &metrics.glyphs {
if glyph.byte_index == min_select.saturating_sub(start) {
selection_start_x = glyph.x - glyph.bearing_x;
selection_start_x = PhysicalLength::new(glyph.x - glyph.bearing_x);
}
if glyph.byte_index == max_select - start
|| glyph.byte_index >= to_draw.len()
{
after_selection_x = glyph.x - glyph.bearing_x;
after_selection_x = PhysicalLength::new(glyph.x - glyph.bearing_x);
break;
}
selection_end_x = glyph.x + glyph.advance_x;
selection_end_x = PhysicalLength::new(glyph.x + glyph.advance_x);
}
let selection_rect = Rect::new(
pos + euclid::vec2(selection_start_x, 0.),
Size::new(selection_end_x - selection_start_x, font_height),
let selection_rect = PhysicalRect::new(
pos + PhysicalPoint::from_lengths(
selection_start_x,
PhysicalLength::default(),
)
.to_vector(),
PhysicalSize::from_lengths(
selection_end_x - selection_start_x,
font_height,
),
);
canvas.fill_path(
&mut rect_to_path(selection_rect),
@ -380,7 +402,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
.unwrap();
canvas
.fill_text(
pos.x + selection_start_x,
pos.x + selection_start_x.get(),
pos.y,
&to_draw[min_select.saturating_sub(start)
..(max_select - start).min(to_draw.len())]
@ -390,7 +412,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
.unwrap();
canvas
.fill_text(
pos.x + after_selection_x,
pos.x + after_selection_x.get(),
pos.y,
&to_draw[(max_select - start).min(to_draw.len())..].trim_end(),
paint,
@ -406,31 +428,36 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
&& cursor_pos == text.len()
&& !text.ends_with('\n')))
{
let cursor_x = metrics
.glyphs
.iter()
.find_map(|glyph| {
if glyph.byte_index == (cursor_pos as usize - start) {
Some(glyph.x)
} else {
None
}
})
.unwrap_or_else(|| metrics.width());
cursor_point = Some([pos.x + cursor_x, pos.y].into());
let cursor_x = PhysicalLength::new(
metrics
.glyphs
.iter()
.find_map(|glyph| {
if glyph.byte_index == (cursor_pos as usize - start) {
Some(glyph.x)
} else {
None
}
})
.unwrap_or_else(|| metrics.width()),
);
cursor_point = Some(PhysicalPoint::from_lengths(
pos.x_length() + cursor_x,
pos.y_length(),
));
}
},
);
if let Some(cursor_point) =
cursor_point.or_else(|| cursor_visible.then(|| [0., next_y].into()))
{
if let Some(cursor_point) = cursor_point.or_else(|| {
cursor_visible.then(|| PhysicalPoint::from_lengths(PhysicalLength::default(), next_y))
}) {
let mut cursor_rect = femtovg::Path::new();
cursor_rect.rect(
cursor_point.x,
cursor_point.y,
text_input.text_cursor_width() * self.scale_factor,
font_height,
(LogicalLength::new(text_input.text_cursor_width()) * self.scale_factor).get(),
font_height.get(),
);
canvas.fill_path(&mut cursor_rect, paint);
}
@ -474,32 +501,34 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
} else {
Solidity::Solid
});
femtovg_path.move_to(at.x * self.scale_factor, at.y * self.scale_factor);
femtovg_path
.move_to(at.x * self.scale_factor.get(), at.y * self.scale_factor.get());
orient.area = 0.;
orient.prev = at;
}
lyon_path::Event::Line { from: _, to } => {
femtovg_path.line_to(to.x * self.scale_factor, to.y * self.scale_factor);
femtovg_path
.line_to(to.x * self.scale_factor.get(), to.y * self.scale_factor.get());
orient.add_point(to);
}
lyon_path::Event::Quadratic { from: _, ctrl, to } => {
femtovg_path.quad_to(
ctrl.x * self.scale_factor,
ctrl.y * self.scale_factor,
to.x * self.scale_factor,
to.y * self.scale_factor,
ctrl.x * self.scale_factor.get(),
ctrl.y * self.scale_factor.get(),
to.x * self.scale_factor.get(),
to.y * self.scale_factor.get(),
);
orient.add_point(to);
}
lyon_path::Event::Cubic { from: _, ctrl1, ctrl2, to } => {
femtovg_path.bezier_to(
ctrl1.x * self.scale_factor,
ctrl1.y * self.scale_factor,
ctrl2.x * self.scale_factor,
ctrl2.y * self.scale_factor,
to.x * self.scale_factor,
to.y * self.scale_factor,
ctrl1.x * self.scale_factor.get(),
ctrl1.y * self.scale_factor.get(),
ctrl2.x * self.scale_factor.get(),
ctrl2.y * self.scale_factor.get(),
to.x * self.scale_factor.get(),
to.y * self.scale_factor.get(),
);
orient.add_point(to);
}
@ -527,7 +556,9 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
let border_paint =
self.brush_to_paint(path.stroke(), &mut femtovg_path).map(|mut paint| {
paint.set_line_width(path.stroke_width() * self.scale_factor);
paint.set_line_width(
(LogicalLength::new(path.stroke_width()) * self.scale_factor).get(),
);
paint
});
@ -570,8 +601,10 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
let height = shadow_options.height;
let radius = shadow_options.radius;
let shadow_rect: euclid::Rect<f32, euclid::UnknownUnit> =
euclid::rect(0., 0., width + 2. * blur, height + 2. * blur);
let shadow_rect = PhysicalRect::new(
PhysicalPoint::default(),
PhysicalSize::from_lengths(width + blur * 2., height + blur * 2.),
);
let shadow_image_width = shadow_rect.width().ceil() as u32;
let shadow_image_height = shadow_rect.height().ceil() as u32;
@ -600,16 +633,22 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
);
let mut shadow_path = femtovg::Path::new();
shadow_path.rounded_rect(blur, blur, width, height, radius);
shadow_path.rounded_rect(
blur.get(),
blur.get(),
width.get(),
height.get(),
radius.get(),
);
canvas.fill_path(
&mut shadow_path,
femtovg::Paint::color(femtovg::Color::rgb(255, 255, 255)),
);
}
let shadow_image = if blur > 0. {
let shadow_image = if blur.get() > 0. {
let blurred_image = shadow_image
.filter(femtovg::ImageFilter::GaussianBlur { sigma: blur / 2. });
.filter(femtovg::ImageFilter::GaussianBlur { sigma: blur.get() / 2. });
self.canvas.borrow_mut().set_render_target(blurred_image.as_render_target());
@ -664,10 +703,10 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
);
self.canvas.borrow_mut().save_with(|canvas| {
let blur = box_shadow.blur() * self.scale_factor;
let offset_x = box_shadow.offset_x() * self.scale_factor;
let offset_y = box_shadow.offset_y() * self.scale_factor;
canvas.translate(offset_x - blur, offset_y - blur);
let blur = LogicalLength::new(box_shadow.blur()) * self.scale_factor;
let offset =
LogicalPoint::new(box_shadow.offset_x(), box_shadow.offset_y()) * self.scale_factor;
canvas.translate(offset.x - blur.get(), offset.y - blur.get());
canvas.fill_path(&mut shadow_image_rect, shadow_image_paint);
});
}
@ -697,20 +736,20 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
return RenderingResult::ContinueRenderingChildren;
}
let geometry = clip_item.as_ref().geometry();
let geometry = clip_item.as_ref().logical_geometry();
// If clipping is enabled but the clip element is outside the visible range, then we don't
// need to bother doing anything, not even rendering the children.
if !self.get_current_clip().intersects(&geometry) {
if !self.get_current_clip().intersects(&geometry.to_untyped()) {
return RenderingResult::ContinueRenderingWithoutChildren;
}
let radius = clip_item.border_radius();
let border_width = clip_item.border_width();
let radius = LogicalLength::new(clip_item.border_radius());
let border_width = LogicalLength::new(clip_item.border_width());
if radius > 0. {
if radius.get() > 0. {
if let Some(layer_image) =
self.render_layer(item_rc, &|| clip_item.as_ref().geometry().size)
self.render_layer(item_rc, &|| clip_item.as_ref().logical_geometry().size)
{
let layer_image_paint = layer_image.as_paint();
@ -727,16 +766,21 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
RenderingResult::ContinueRenderingWithoutChildren
} else {
self.graphics_cache.release(item_rc);
// Note: This is correct, combine_clip is API and operates on logical coordinates.
self.combine_clip(
euclid::rect(0., 0., geometry.width(), geometry.height()),
radius,
border_width,
radius.get(),
border_width.get(),
);
RenderingResult::ContinueRenderingChildren
}
}
fn combine_clip(&mut self, clip_rect: Rect, radius: f32, border_width: f32) -> bool {
let clip_rect = LogicalRect::from_untyped(&clip_rect);
let radius = LogicalLength::new(radius);
let border_width = LogicalLength::new(border_width);
let clip = &mut self.state.last_mut().unwrap().scissor;
let clip_region_valid = match clip.intersection(&clip_rect) {
Some(r) => {
@ -744,7 +788,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
true
}
None => {
*clip = Rect::default();
*clip = LogicalRect::default();
false
}
};
@ -763,13 +807,13 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
// femtovg only supports rectangular clipping. Non-rectangular clips must be handled via `apply_clip`,
// which can render children into a layer.
debug_assert!(radius == 0.);
debug_assert!(radius.get() == 0.);
clip_region_valid
}
fn get_current_clip(&self) -> Rect {
self.state.last().unwrap().scissor
self.state.last().unwrap().scissor.to_untyped()
}
fn save_state(&mut self) {
@ -783,7 +827,7 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
}
fn scale_factor(&self) -> f32 {
self.scale_factor
self.scale_factor.get()
}
fn draw_cached_pixmap(
@ -825,7 +869,8 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
fn draw_string(&mut self, string: &str, color: Color) {
let font = fonts::FONT_CACHE
.with(|cache| cache.borrow_mut().font(Default::default(), self.scale_factor, string));
let paint = font.init_paint(0.0, femtovg::Paint::color(to_femtovg_color(&color)));
let paint = font
.init_paint(PhysicalLength::default(), femtovg::Paint::color(to_femtovg_color(&color)));
let mut canvas = self.canvas.borrow_mut();
canvas.fill_text(0., 0., string, paint).unwrap();
}
@ -839,7 +884,9 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
}
fn translate(&mut self, x: f32, y: f32) {
self.canvas.borrow_mut().translate(x * self.scale_factor, y * self.scale_factor);
self.canvas
.borrow_mut()
.translate(x * self.scale_factor.get(), y * self.scale_factor.get());
let clip = &mut self.state.last_mut().unwrap().scissor;
*clip = clip.translate((-x, -y).into())
}
@ -850,24 +897,24 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {
let clip = &mut self.state.last_mut().unwrap().scissor;
// Compute the bounding box of the rotated rectangle
let (sin, cos) = angle_in_radians.sin_cos();
let rotate_point = |p: Point| (p.x * cos - p.y * sin, p.x * sin + p.y * cos);
let rotate_point = |p: LogicalPoint| (p.x * cos - p.y * sin, p.x * sin + p.y * cos);
let corners = [
rotate_point(clip.origin),
rotate_point(clip.origin + euclid::vec2(clip.width(), 0.)),
rotate_point(clip.origin + euclid::vec2(0., clip.height())),
rotate_point(clip.origin + clip.size),
];
let origin: Point = (
let origin: LogicalPoint = (
corners.iter().fold(f32::MAX, |a, b| b.0.min(a)),
corners.iter().fold(f32::MAX, |a, b| b.1.min(a)),
)
.into();
let end: Point = (
let end: LogicalPoint = (
corners.iter().fold(f32::MIN, |a, b| b.0.max(a)),
corners.iter().fold(f32::MIN, |a, b| b.1.max(a)),
)
.into();
*clip = Rect::new(origin, (end - origin).into());
*clip = LogicalRect::new(origin, (end - origin).into());
}
fn apply_opacity(&mut self, opacity: f32) {
@ -888,7 +935,7 @@ impl<'a> GLItemRenderer<'a> {
width: u32,
height: u32,
) -> Self {
let scale_factor = window.scale_factor();
let scale_factor = ScaleFactor::new(window.scale_factor());
Self {
graphics_cache: &canvas.graphics_cache,
texture_cache: &canvas.texture_cache,
@ -898,9 +945,9 @@ impl<'a> GLItemRenderer<'a> {
window,
scale_factor,
state: vec![State {
scissor: Rect::new(
Point::default(),
Size::new(width as f32, height as f32) / scale_factor,
scissor: LogicalRect::new(
LogicalPoint::default(),
PhysicalSize::new(width as f32, height as f32) / scale_factor,
),
global_alpha: 1.,
current_render_target: femtovg::RenderTarget::Screen,
@ -912,7 +959,7 @@ impl<'a> GLItemRenderer<'a> {
fn render_layer(
&mut self,
item_rc: &ItemRc,
layer_logical_size_fn: &dyn Fn() -> Size,
layer_logical_size_fn: &dyn Fn() -> LogicalSize,
) -> Option<Rc<Texture>> {
let existing_layer_texture =
self.graphics_cache.with_entry(item_rc, |cache_entry| match cache_entry {
@ -922,7 +969,7 @@ impl<'a> GLItemRenderer<'a> {
let cache_entry = self.graphics_cache.get_or_update_cache_entry(item_rc, || {
ItemGraphicsCacheEntry::Texture({
let size: IntSize = (layer_logical_size_fn() * self.scale_factor).ceil().cast();
let size = (layer_logical_size_fn() * self.scale_factor).ceil().cast();
let layer_image = existing_layer_texture
.and_then(|layer_texture| {
@ -931,7 +978,7 @@ impl<'a> GLItemRenderer<'a> {
// Then it is safe to render new content into it in this callback and when we return
// into `get_or_update_cache_entry` the first ref is dropped.
debug_assert_eq!(Rc::strong_count(&layer_texture), 2);
if layer_texture.size() == Some(size) {
if layer_texture.size() == Some(size.to_untyped()) {
Some(layer_texture)
} else {
None
@ -962,9 +1009,10 @@ impl<'a> GLItemRenderer<'a> {
}
*self.state.last_mut().unwrap() = State {
scissor: Rect::new(
Point::default(),
Size::new(size.width as f32, size.height as f32),
scissor: LogicalRect::new(
LogicalPoint::default(),
PhysicalSize::new(size.width as f32, size.height as f32)
/ self.scale_factor,
),
global_alpha: 1.,
current_render_target: layer_image.as_render_target(),
@ -998,13 +1046,13 @@ impl<'a> GLItemRenderer<'a> {
// We don't need to include the size of the opacity item itself, since it has no content.
let children_rect = i_slint_core::properties::evaluate_no_tracking(|| {
let self_ref = item_rc.borrow();
self_ref.as_ref().geometry().union(
LogicalRect::from_untyped(&self_ref.as_ref().geometry().union(
&i_slint_core::item_rendering::item_children_bounding_rect(
&item_rc.component(),
item_rc.index() as isize,
&current_clip,
),
)
))
});
children_rect.size
})
@ -1109,10 +1157,10 @@ impl<'a> GLItemRenderer<'a> {
colorize_property: Option<Pin<&Property<Brush>>>,
image_rendering: ImageRendering,
) {
let target_w = target_width.get() * self.scale_factor;
let target_h = target_height.get() * self.scale_factor;
let target_w = LogicalLength::new(target_width.get()) * self.scale_factor;
let target_h = LogicalLength::new(target_height.get()) * self.scale_factor;
if target_w <= 0. || target_h <= 0. {
if target_w.get() <= 0. || target_h.get() <= 0. {
return;
}
@ -1199,27 +1247,27 @@ impl<'a> GLItemRenderer<'a> {
// The source_to_target scale is applied to the paint that holds the image as well as path
// begin rendered.
let (source_to_target_scale_x, source_to_target_scale_y) = match image_fit {
ImageFit::Fill => (target_w / source_width, target_h / source_height),
ImageFit::Fill => (target_w.get() / source_width, target_h.get() / source_height),
ImageFit::Cover => {
let ratio = f32::max(target_w / source_width, target_h / source_height);
let ratio = f32::max(target_w.get() / source_width, target_h.get() / source_height);
if source_width > target_w / ratio {
source_x += (source_width - target_w / ratio) / 2.;
if source_width > target_w.get() / ratio {
source_x += (source_width - target_w.get() / ratio) / 2.;
}
if source_height > target_h / ratio {
source_y += (source_height - target_h / ratio) / 2.
if source_height > target_h.get() / ratio {
source_y += (source_height - target_h.get() / ratio) / 2.
}
(ratio, ratio)
}
ImageFit::Contain => {
let ratio = f32::min(target_w / source_width, target_h / source_height);
let ratio = f32::min(target_w.get() / source_width, target_h.get() / source_height);
if source_width < target_w / ratio {
image_fit_offset.x = (target_w - source_width * ratio) / 2.;
if source_width < target_w.get() / ratio {
image_fit_offset.x = (target_w.get() - source_width * ratio) / 2.;
}
if source_height < target_h / ratio {
image_fit_offset.y = (target_h - source_height * ratio) / 2.
if source_height < target_h.get() / ratio {
image_fit_offset.y = (target_h.get() - source_height * ratio) / 2.
}
(ratio, ratio)

View file

@ -574,11 +574,11 @@ impl<'a> ItemRenderer for SkiaRenderer<'a> {
self_rc,
self.image_cache,
box_shadow,
self.scale_factor.get(),
self.scale_factor,
|shadow_options| {
let shadow_size: skia_safe::Size = (
shadow_options.width + 2. * shadow_options.blur,
shadow_options.height + 2. * shadow_options.blur,
shadow_options.width.get() + shadow_options.blur.get() * 2.,
shadow_options.height.get() + shadow_options.blur.get() * 2.,
)
.into();
@ -591,13 +591,13 @@ impl<'a> ItemRenderer for SkiaRenderer<'a> {
let rounded_rect = skia_safe::RRect::new_rect_xy(
skia_safe::Rect::from_xywh(
shadow_options.blur,
shadow_options.blur,
shadow_options.width,
shadow_options.height,
shadow_options.blur.get(),
shadow_options.blur.get(),
shadow_options.width.get(),
shadow_options.height.get(),
),
shadow_options.radius,
shadow_options.radius,
shadow_options.radius.get(),
shadow_options.radius.get(),
);
let mut paint = skia_safe::Paint::default();
@ -605,7 +605,7 @@ impl<'a> ItemRenderer for SkiaRenderer<'a> {
paint.set_anti_alias(true);
paint.set_mask_filter(skia_safe::MaskFilter::blur(
skia_safe::BlurStyle::Normal,
shadow_options.blur / 2.,
shadow_options.blur.get() / 2.,
None,
));