use std::rc::Rc;
use super::commands::{Border, CornerRadii, DrawCommand};
use super::text_measurer::LineFit;
use super::tree::{ClipRegion, NodeId, RenderNode};
use super::types::{Gradient, Shadow};
use crate::image_decode::DecodedImage;
use crate::pivot::Pivot;
use crate::transform::Transform;
use crate::tree::{Tree, WidgetId};
use crate::widgets::font::{FontFamily, FontWeight, LineHeight};
use crate::widgets::image::{ContentFit, ImageSource};
use crate::widgets::text_style::{TextShadow, TextStroke};
use crate::widgets::{Color, Rect, TextAlign};
pub struct PaintContext<'a> {
node: &'a mut RenderNode,
cull_rect: Option<Rect>,
tree: &'a Tree,
id: WidgetId,
scale_factor: f32,
}
impl<'a> PaintContext<'a> {
pub(crate) fn new(
node: &'a mut RenderNode,
tree: &'a Tree,
id: WidgetId,
scale_factor: f32,
) -> Self {
Self {
node,
cull_rect: None,
tree,
id,
scale_factor,
}
}
pub fn id(&self) -> WidgetId {
self.id
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn tree(&self) -> &'a Tree {
self.tree
}
pub fn cull_rect(&self) -> Option<Rect> {
self.cull_rect
}
pub fn set_cull_rect(&mut self, rect: Rect) {
self.cull_rect = Some(rect);
}
pub fn mark_partial(&mut self) {
self.node.partial = true;
}
pub fn set_bounds(&mut self, bounds: Rect) {
self.node.bounds = bounds;
}
pub(crate) fn set_transform(&mut self, transform: Transform) {
self.node.local_transform = transform;
self.node.parent_position = transform;
}
pub fn apply_transform(&mut self, transform: Transform) {
if !transform.is_identity() {
self.node.local_transform = self.node.local_transform.then(&transform);
self.cull_rect_follows(transform);
}
}
pub fn apply_transform_with_pivot(&mut self, transform: Transform, origin: Pivot) {
if !transform.is_identity() {
self.node.pivot = origin;
self.apply_transform(transform);
}
}
fn cull_rect_follows(&mut self, transform: Transform) {
let Some(cull) = self.cull_rect else {
return;
};
self.cull_rect = Some(
transform
.about(self.node.pivot, self.node.bounds)
.inverse()
.map_or(Rect::default(), |back| back.map_rect(cull)),
);
}
pub fn set_opacity(&mut self, opacity: f32) {
self.node.opacity = opacity.clamp(0.0, 1.0);
}
pub fn set_pivot(&mut self, origin: Pivot) {
self.node.pivot = origin;
}
pub fn set_transform_with_pivot(&mut self, transform: Transform, origin: Pivot) {
self.node.local_transform = transform;
self.node.pivot = origin;
}
pub fn set_clip(
&mut self,
rect: Rect,
corner_radius: crate::renderer::CornerRadii,
curvature: f32,
) {
self.node.clip = Some(ClipRegion {
rect,
corner_radius,
curvature,
});
}
pub fn set_clip_rect(&mut self, rect: Rect) {
self.set_clip(rect, crate::renderer::CornerRadii::uniform(0.0), 1.0);
}
pub fn set_overlay_clip(
&mut self,
rect: Rect,
corner_radius: crate::renderer::CornerRadii,
curvature: f32,
) {
self.node.overlay_clip = Some(ClipRegion {
rect,
corner_radius,
curvature,
});
}
pub fn draw_rounded_rect(&mut self, rect: Rect, color: Color, radius: impl Into<CornerRadii>) {
self.node.commands.push(Rc::new(DrawCommand::RoundedRect {
rect,
color,
radius: radius.into(),
curvature: 1.0,
border: None,
shadow: None,
gradient: None,
}));
}
pub fn draw_rounded_rect_with_curvature(
&mut self,
rect: Rect,
color: Color,
radius: impl Into<CornerRadii>,
curvature: f32,
) {
self.node.commands.push(Rc::new(DrawCommand::RoundedRect {
rect,
color,
radius: radius.into(),
curvature,
border: None,
shadow: None,
gradient: None,
}));
}
pub fn draw_border_frame_with_curvature(
&mut self,
rect: Rect,
border_color: Color,
radius: impl Into<CornerRadii>,
border_width: f32,
curvature: f32,
) {
self.node.commands.push(Rc::new(DrawCommand::RoundedRect {
rect,
color: Color::TRANSPARENT,
radius: radius.into(),
curvature,
border: Some(Border::new(border_width, border_color)),
shadow: None,
gradient: None,
}));
}
pub fn draw_rounded_rect_with_shadow(
&mut self,
rect: Rect,
color: Color,
radius: impl Into<CornerRadii>,
curvature: f32,
shadow: Shadow,
) {
self.node.commands.push(Rc::new(DrawCommand::RoundedRect {
rect,
color,
radius: radius.into(),
curvature,
border: None,
shadow: Some(shadow),
gradient: None,
}));
}
#[allow(clippy::too_many_arguments)]
pub fn draw_rounded_rect_full(
&mut self,
rect: Rect,
color: Color,
radius: impl Into<CornerRadii>,
curvature: f32,
border: Option<Border>,
shadow: Option<Shadow>,
gradient: Option<Gradient>,
) {
self.node.commands.push(Rc::new(DrawCommand::RoundedRect {
rect,
color,
radius: radius.into(),
curvature,
border,
shadow,
gradient,
}));
}
pub fn declare_input_region(
&mut self,
rect: Rect,
corner_radii: impl Into<CornerRadii>,
curvature: f32,
takes: bool,
) {
self.node.commands.push(Rc::new(DrawCommand::InputRegion {
rect,
corner_radii: corner_radii.into(),
curvature,
takes,
}));
}
pub fn draw_backdrop_blur(
&mut self,
rect: Rect,
sources: crate::backdrop::BackdropSources,
radius: f32,
corner_radii: impl Into<CornerRadii>,
curvature: f32,
) {
self.node.commands.push(Rc::new(DrawCommand::BackdropBlur {
rect,
sources,
radius,
corner_radii: corner_radii.into(),
curvature,
}));
}
#[allow(clippy::too_many_arguments)]
pub fn draw_text_backdrop_blur(
&mut self,
text: &str,
rect: Rect,
radius: f32,
stroke: Option<TextStroke>,
font_size: f32,
font_family: FontFamily,
font_weight: FontWeight,
line_height: LineHeight,
letter_spacing: f32,
align: TextAlign,
fit: Option<LineFit>,
) {
if text.is_empty() || radius <= 0.0 {
return;
}
self.node
.commands
.push(Rc::new(DrawCommand::TextBackdropBlur {
text: text.to_owned(),
stroke: stroke.filter(|s| s.width > 0.0),
rect,
radius,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
align,
fit,
}));
}
pub fn draw_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
self.node.commands.push(Rc::new(DrawCommand::Circle {
center: (cx, cy),
radius,
color,
}));
}
pub fn draw_text(&mut self, text: &str, rect: Rect, color: Color, font_size: f32) {
self.draw_text_styled(
text,
rect,
color,
font_size,
FontFamily::default(),
FontWeight::NORMAL,
LineHeight::Normal,
0.0,
None,
);
}
#[allow(clippy::too_many_arguments)]
pub fn draw_text_styled(
&mut self,
text: &str,
rect: Rect,
color: Color,
font_size: f32,
font_family: FontFamily,
font_weight: FontWeight,
line_height: LineHeight,
letter_spacing: f32,
fit: Option<LineFit>,
) {
if text.is_empty() {
return;
}
self.push_text(
&Rc::from(text),
rect,
color,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
TextAlign::Start,
fit,
);
}
#[allow(clippy::too_many_arguments)]
fn push_text(
&mut self,
text: &Rc<str>,
rect: Rect,
color: Color,
font_size: f32,
font_family: FontFamily,
font_weight: FontWeight,
line_height: LineHeight,
letter_spacing: f32,
align: TextAlign,
fit: Option<LineFit>,
) {
self.node.commands.push(Rc::new(DrawCommand::Text {
text: Rc::clone(text),
rect,
color,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
align,
fit,
}));
}
#[allow(clippy::too_many_arguments)]
pub fn draw_text_decorated(
&mut self,
text: &str,
rect: Rect,
color: Color,
font_size: f32,
font_family: FontFamily,
font_weight: FontWeight,
line_height: LineHeight,
letter_spacing: f32,
align: TextAlign,
stroke: Option<TextStroke>,
shadow: Option<TextShadow>,
fit: Option<LineFit>,
) {
if text.is_empty() {
return;
}
let text = &Rc::from(text);
if let Some(shadow) = shadow.filter(|s| s.color.a > 0.0) {
for (dx, dy, sample_color) in shadow.samples() {
self.push_text(
text,
rect.offset(dx, dy),
sample_color,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
align,
fit,
);
}
}
if let Some(stroke) = stroke.filter(|s| s.width > 0.0) {
for (dx, dy) in stroke.samples() {
self.push_text(
text,
rect.offset(dx, dy),
stroke.color,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
align,
fit,
);
}
}
self.push_text(
text,
rect,
color,
font_size,
font_family,
font_weight,
line_height,
letter_spacing,
align,
fit,
);
}
pub fn draw_image(
&mut self,
source: ImageSource,
rect: Rect,
content_fit: ContentFit,
tint: Option<Color>,
) {
let decoded = match crate::image_decode::state(&source) {
None => None,
Some((state, decoded)) if crate::image_decode::paints(state, &decoded) => Some(decoded),
Some(_) => return,
};
self.push_image(source, decoded, rect, content_fit, tint);
}
pub(crate) fn push_image(
&mut self,
source: ImageSource,
decoded: Option<DecodedImage>,
rect: Rect,
content_fit: ContentFit,
tint: Option<Color>,
) {
self.node.commands.push(Rc::new(DrawCommand::Image {
source,
decoded,
rect,
content_fit,
tint,
}));
}
pub(crate) fn add_child(&mut self, widget: WidgetId, bounds: Rect) -> PaintContext<'_> {
let id: NodeId = widget.as_u64();
self.node
.children
.push(Rc::new(RenderNode::with_bounds(id, bounds)));
let child = Rc::get_mut(
self.node
.children
.last_mut()
.expect("child was just pushed"),
)
.expect("freshly created child Rc is unique");
PaintContext::new(child, self.tree, widget, self.scale_factor)
}
pub(crate) fn add_child_rc(&mut self, node: Rc<RenderNode>) {
self.node.children.push(node);
}
pub fn draw_overlay_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
self.node
.overlay_commands
.push(Rc::new(DrawCommand::Circle {
center: (cx, cy),
radius,
color,
}));
}
pub fn draw_overlay_rounded_rect(
&mut self,
rect: Rect,
color: Color,
radius: impl Into<CornerRadii>,
) {
self.node
.overlay_commands
.push(Rc::new(DrawCommand::RoundedRect {
rect,
color,
radius: radius.into(),
curvature: 1.0,
border: None,
shadow: None,
gradient: None,
}));
}
}