use super::*;
pub(super) fn layer_surface(width: f32, height: f32) -> Result<Surface, String> {
surfaces::raster_n32_premul((width.ceil().max(1.0) as i32, height.ceil().max(1.0) as i32))
.ok_or_else(|| "Skia could not create an offscreen layer".to_owned())
}
pub(super) fn draw_rect(canvas: &Canvas, rect: UiRect, style: VisualStyle) {
let area = sk_rect(rect);
if let Some(fill) = style.fill {
let paint = color_paint(fill, style.fill_alpha);
if style.radius > 0.0 {
canvas.draw_rrect(RRect::new_rect_xy(area, style.radius, style.radius), &paint);
} else {
canvas.draw_rect(area, &paint);
}
}
if let Some(stroke) = style.stroke {
let paint = stroke_paint(stroke);
if style.radius > 0.0 {
canvas.draw_rrect(RRect::new_rect_xy(area, style.radius, style.radius), &paint);
} else {
canvas.draw_rect(area, &paint);
}
}
}
pub(super) fn draw_ellipse(canvas: &Canvas, rect: UiRect, style: VisualStyle) {
if let Some(fill) = style.fill {
canvas.draw_oval(sk_rect(rect), &color_paint(fill, style.fill_alpha));
}
if let Some(stroke) = style.stroke {
canvas.draw_oval(sk_rect(rect), &stroke_paint(stroke));
}
}
pub(super) fn draw_path(canvas: &Canvas, path: &UiPath, style: PathStyle) {
let path = sk_path(path);
if let Some(fill) = style.fill {
canvas.draw_path(&path, &color_paint(fill, style.fill_alpha));
}
if let Some(stroke) = style.stroke {
canvas.draw_path(&path, &stroke_paint(stroke));
}
}
pub(super) fn draw_image(
canvas: &Canvas,
image: &Image,
rect: UiRect,
fit: ImageFit,
paint: Option<&Paint>,
) {
let image_size = (image.width() as f32, image.height() as f32);
let destination = fitted_rect(rect, image_size, fit);
let default_paint = Paint::default();
if fit == ImageFit::Cover {
canvas.save();
canvas.clip_rect(sk_rect(rect), None, true);
}
canvas.draw_image_rect_with_sampling_options(
image,
None,
sk_rect(destination),
SamplingOptions::default(),
paint.unwrap_or(&default_paint),
);
if fit == ImageFit::Cover {
canvas.restore();
}
}
pub(super) fn draw_glow(canvas: &Canvas, rect: UiRect, color: Color, alpha: u8) {
let center = (
rect.left + rect.width() / 2.0,
rect.top + rect.height() / 2.0,
);
let colors = [
sk_color_f(color, alpha as f32 / 255.0),
sk_color_f(color, 0.0),
];
let positions = [0.0, 1.0];
let gradient = skia_safe::gradient::Gradient::new(
skia_safe::gradient::Colors::new(
colors.as_slice(),
Some(positions.as_slice()),
TileMode::Clamp,
None,
),
skia_safe::gradient::Interpolation::default(),
);
if let Some(shader) = skia_safe::gradient::shaders::radial_gradient(
(center, rect.width().max(rect.height()) / 2.0),
&gradient,
None,
) {
let mut paint = Paint::default();
paint.set_shader(shader);
canvas.draw_rect(sk_rect(rect), &paint);
}
}
pub(super) fn draw_overlay(canvas: &Canvas, rect: UiRect, style: &lgui_core::core::OverlayStyle) {
for layer in &style.vertical_layers {
let colors = [
sk_color_f(layer.color, layer.alpha_top),
sk_color_f(layer.color, layer.alpha_bottom),
];
let gradient = skia_safe::gradient::Gradient::new(
skia_safe::gradient::Colors::new_evenly_spaced(
colors.as_slice(),
TileMode::Clamp,
None,
),
skia_safe::gradient::Interpolation::default(),
);
if let Some(shader) = skia_safe::gradient::shaders::linear_gradient(
((rect.left, rect.top), (rect.left, rect.bottom)),
&gradient,
None,
) {
let mut paint = Paint::default();
paint.set_shader(shader);
canvas.draw_rect(sk_rect(rect), &paint);
}
}
for layer in &style.radial_layers {
let center = (
rect.left + rect.width() * layer.center_x,
rect.top + rect.height() * layer.center_y,
);
let radius = rect.width().min(rect.height()) * layer.radius;
let colors = [
sk_color_f(layer.color, layer.alpha),
sk_color_f(layer.color, 0.0),
];
let gradient = skia_safe::gradient::Gradient::new(
skia_safe::gradient::Colors::new_evenly_spaced(
colors.as_slice(),
TileMode::Clamp,
None,
),
skia_safe::gradient::Interpolation::default(),
);
if let Some(shader) =
skia_safe::gradient::shaders::radial_gradient((center, radius), &gradient, None)
{
let mut paint = Paint::default();
paint.set_shader(shader);
canvas.draw_rect(sk_rect(rect), &paint);
}
}
}
pub(super) fn draw_composited(
canvas: &Canvas,
image: &Image,
rect: UiRect,
opacity: u8,
transform: LayerTransform,
) {
canvas.save();
let origin = (
rect.left + rect.width() * transform.origin_x(),
rect.top + rect.height() * transform.origin_y(),
);
canvas.translate((
origin.0 + transform.translation_x(),
origin.1 + transform.translation_y(),
));
canvas.rotate(transform.rotation_degrees_f32(), None);
canvas.scale((transform.scale_x(), transform.scale_y()));
canvas.translate((-origin.0, -origin.1));
let mut paint = Paint::default();
paint.set_alpha(opacity);
let destination = sk_rect(rect);
canvas.draw_image_rect(image, None, &destination, &paint);
canvas.restore();
}
pub(super) fn custom_scene(
key: &str,
rect: UiRect,
style: lgui_core::core::CustomPaintStyle,
) -> Result<Option<lgui_assets::SceneFragment>, String> {
let Some(provider) = render_resources().custom_paint().cloned() else {
return Ok(None);
};
provider
.record(key, rect, style)
.map_err(|error| error.to_string())
}
pub(super) fn fitted_rect(bounds: UiRect, image: (f32, f32), fit: ImageFit) -> UiRect {
if fit == ImageFit::Fill || image.0 <= 0.0 || image.1 <= 0.0 {
return bounds;
}
let scale = match fit {
ImageFit::Contain => (bounds.width() / image.0).min(bounds.height() / image.1),
ImageFit::Cover => (bounds.width() / image.0).max(bounds.height() / image.1),
ImageFit::Fill => 1.0,
};
let width = image.0 * scale;
let height = image.1 * scale;
UiRect::new(
bounds.left + (bounds.width() - width) / 2.0,
bounds.top + (bounds.height() - height) / 2.0,
bounds.left + (bounds.width() + width) / 2.0,
bounds.top + (bounds.height() + height) / 2.0,
)
}
pub(super) fn sk_path(path: &UiPath) -> Path {
let mut result = PathBuilder::new();
for command in path.commands() {
match command {
UiPathCommand::MoveTo(point) => {
result.move_to((point.x, point.y));
}
UiPathCommand::LineTo(point) => {
result.line_to((point.x, point.y));
}
UiPathCommand::QuadraticTo { control, to } => {
result.quad_to((control.x, control.y), (to.x, to.y));
}
UiPathCommand::CubicTo {
control1,
control2,
to,
} => {
result.cubic_to(
(control1.x, control1.y),
(control2.x, control2.y),
(to.x, to.y),
);
}
UiPathCommand::Close => {
result.close();
}
}
}
result.detach()
}
pub(super) fn stroke_paint(stroke: Stroke) -> Paint {
let mut paint = color_paint(stroke.color, stroke.alpha);
paint.set_style(PaintStyle::Stroke);
paint.set_stroke_width(stroke.width.max(0.0));
paint
}
pub(super) fn color_paint(color: Color, alpha: u8) -> Paint {
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_color(sk_color(color, alpha));
paint
}
pub(super) fn sk_color(color: Color, alpha: u8) -> SkColor {
SkColor::from_argb(
alpha,
((color.0 >> 16) & 0xFF) as u8,
((color.0 >> 8) & 0xFF) as u8,
(color.0 & 0xFF) as u8,
)
}
pub(super) fn sk_color_f(color: Color, alpha: f32) -> Color4f {
Color4f::new(
((color.0 >> 16) & 0xFF) as f32 / 255.0,
((color.0 >> 8) & 0xFF) as f32 / 255.0,
(color.0 & 0xFF) as f32 / 255.0,
alpha.clamp(0.0, 1.0),
)
}
pub(super) fn sk_rect(rect: UiRect) -> Rect {
Rect::new(rect.left, rect.top, rect.right, rect.bottom)
}
pub(super) fn physical_rect(rect: PhysicalRect) -> Rect {
Rect::new(
rect.left as f32,
rect.top as f32,
rect.right as f32,
rect.bottom as f32,
)
}
pub(super) fn ui_rect_from_physical(rect: PhysicalRect) -> UiRect {
UiRect::new(
rect.left as f32,
rect.top as f32,
rect.right as f32,
rect.bottom as f32,
)
}
pub(super) fn image_key(source: &UiImageSource) -> String {
match source {
UiImageSource::Static(key) => format!("asset:{key}"),
UiImageSource::File(path) => format!("file:{}", path.display()),
UiImageSource::Url(url) => format!("url:{url}"),
UiImageSource::Bytes { key, version, .. } => format!("bytes:{key}:{version}"),
}
}
pub(super) fn tint_svg(svg: &str, color: Color, alpha: u8) -> String {
let hex = format!("#{:06X}", color.0 & 0xFFFFFF);
svg.replace("currentColor", &hex)
.replace("currentOpacity", &format!("{:.6}", alpha as f32 / 255.0))
}