Lightningbeam/lightningbeam-ui/lightningbeam-editor/src/theme_render.rs

107 lines
3.8 KiB
Rust

/// Theme rendering helpers for painting CSS backgrounds
///
/// Handles solid colors, linear gradients (via egui Mesh), and image backgrounds.
use eframe::egui;
use crate::theme::Background;
/// Paint a background into the given rect
pub fn paint_background(
painter: &egui::Painter,
rect: egui::Rect,
background: &Background,
rounding: f32,
) {
match background {
Background::Solid(color) => {
painter.rect_filled(rect, rounding, *color);
}
Background::LinearGradient { angle_degrees, stops } => {
paint_linear_gradient(painter, rect, *angle_degrees, stops, rounding);
}
Background::Image { .. } => {
// Image backgrounds require a TextureHandle loaded externally.
// For now, fall back to transparent (no-op).
// TODO: image cache integration
}
}
}
/// Paint a linear gradient using an egui Mesh with colored vertices
///
/// Supports arbitrary angles. The gradient direction follows CSS conventions:
/// - 0deg = bottom to top
/// - 90deg = left to right
/// - 180deg = top to bottom (default)
/// - 270deg = right to left
pub fn paint_linear_gradient(
painter: &egui::Painter,
rect: egui::Rect,
angle_degrees: f32,
stops: &[(f32, egui::Color32)],
rounding: f32,
) {
if stops.len() < 2 {
if let Some((_, color)) = stops.first() {
painter.rect_filled(rect, rounding, *color);
}
return;
}
// Convert CSS angle to a direction vector
// CSS: 0deg = to top, 90deg = to right, 180deg = to bottom
let angle_rad = (angle_degrees - 90.0).to_radians();
let dir = egui::vec2(angle_rad.cos(), angle_rad.sin());
// Project rect corners onto gradient direction to find start/end
let center = rect.center();
let half_size = rect.size() / 2.0;
// The gradient line length is the projection of the rect diagonal onto the direction
let gradient_half_len = (half_size.x * dir.x.abs()) + (half_size.y * dir.y.abs());
// For simple horizontal/vertical gradients with no rounding, use a mesh directly
if rounding <= 0.0 {
let mut mesh = egui::Mesh::default();
mesh.texture_id = egui::TextureId::default();
// For each consecutive pair of stops, add a quad
for i in 0..stops.len() - 1 {
let (t0, c0) = stops[i];
let (t1, c1) = stops[i + 1];
// Map t to positions along the gradient line
let p0_along = -gradient_half_len + t0 * 2.0 * gradient_half_len;
let p1_along = -gradient_half_len + t1 * 2.0 * gradient_half_len;
// Perpendicular direction for quad width
let perp = egui::vec2(-dir.y, dir.x);
let perp_extent = (half_size.x * perp.x.abs()) + (half_size.y * perp.y.abs());
let base0 = center + dir * p0_along;
let base1 = center + dir * p1_along;
let v0 = base0 - perp * perp_extent;
let v1 = base0 + perp * perp_extent;
let v2 = base1 + perp * perp_extent;
let v3 = base1 - perp * perp_extent;
let uv = egui::pos2(0.0, 0.0);
let idx = mesh.vertices.len() as u32;
mesh.vertices.push(egui::epaint::Vertex { pos: v0, uv, color: c0 });
mesh.vertices.push(egui::epaint::Vertex { pos: v1, uv, color: c0 });
mesh.vertices.push(egui::epaint::Vertex { pos: v2, uv, color: c1 });
mesh.vertices.push(egui::epaint::Vertex { pos: v3, uv, color: c1 });
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx, idx + 2, idx + 3]);
}
painter.add(egui::Shape::mesh(mesh));
} else {
// For rounded rects, paint without rounding for now.
// TODO: proper rounded gradient with tessellation or clip mask
paint_linear_gradient(painter, rect, angle_degrees, stops, 0.0);
}
}