use super::Frame;
use crate::canvas::PREVIEW_COLOR;
use crate::canvas::box_select::draw_band;
use crate::canvas::definition_drawing::{
PixelTint, Placement, Style, draw_definition, draw_photo, mode_color,
};
use crate::canvas::rotated_rect::RotatedRect;
use crate::canvas::shapes::{Shapes, draw_adaptive_grid};
use crate::fixture_definition_editor::canvas_geometry::{
label_font_px, pixel_shape, pixel_shape_of, pixel_under, to_metres,
};
use crate::fixture_definition_editor::draw_tools;
use crate::fixture_definition_editor::generators;
use crate::fixture_definition_editor::state::Drag;
use egui::{
Align2, Color32, FontId, Painter, Pos2, Rect, Response, Shape, Stroke, Vec2, pos2, vec2,
};
use gled_model::patching::fixture::{LayoutPosition, UNITS_PER_METRE};
const INDIVIDUAL_PIXEL: Color32 = Color32::from_gray(170);
const OUTLINE: Color32 = Color32::from_gray(110);
const SELECTED: Color32 = Color32::WHITE;
const HOVERED: Color32 = Color32::from_gray(140);
impl Frame<'_, '_> {
pub(super) fn paint(&mut self, painter: &Painter, asset_id: uuid::Uuid, response: &Response) {
let origin = self.state.origin();
let definition = &*self.edit.value;
let to_screen = self.scene_transform;
let min_half = self.min_half();
painter.add(self.grid(origin));
let placement = Placement {
position: &LayoutPosition::at(origin),
to_global: to_screen,
min_screen_px: 0.0,
};
draw_photo(painter, definition, &placement, &mut self.state.caches);
if definition.pixels.is_empty() && self.state.draw.shape.is_none() {
painter.text(
painter.clip_rect().center() - vec2(0.0, 40.0),
Align2::CENTER_CENTER,
"Draw LEDs: L line · C circle · P path\nor double-click to place one",
FontId::proportional(16.0),
Color32::from_gray(150),
);
}
let mode = definition
.mode(self.state.mode)
.filter(|_| self.state.mode > 0);
let colors: Vec<Color32> = mode
.map(|mode| {
mode.group_of
.iter()
.map(|group| mode_color(Some(*group)))
.collect()
})
.unwrap_or_default();
let style = Style {
outline: OUTLINE,
pixels: if mode.is_some() {
PixelTint::PerPixel(&colors)
} else {
PixelTint::Uniform(INDIVIDUAL_PIXEL)
},
};
draw_definition(
painter,
definition,
Some(asset_id),
&placement,
style,
&mut self.state.caches,
);
for pixel in 0..definition.pixels.len() {
let Some(shape) = pixel_shape(definition, pixel, origin, 0.0) else {
continue;
};
let size_px = 2.0 * shape.half.min_elem() * to_screen.scaling;
if let Some(font) = label_font_px(size_px) {
painter.text(
to_screen * shape.centre,
Align2::CENTER_CENTER,
(pixel + 1).to_string(),
FontId::proportional(font),
Color32::BLACK,
);
}
}
let outline = |shape: RotatedRect, stroke: Stroke| {
Shape::closed_line(
shape.corners().map(|corner| to_screen * corner).to_vec(),
stroke,
)
};
let hovered = response
.hover_pos()
.filter(|_| self.state.drag.is_none())
.and_then(|pointer| pixel_under(definition, origin, pointer, min_half));
if let Some(shape) =
hovered.and_then(|pixel| pixel_shape(definition, pixel, origin, min_half))
{
painter.add(outline(shape, Stroke::new(1.5, HOVERED)));
}
for pixel in &self.state.selection {
if let Some(shape) = pixel_shape(definition, *pixel, origin, min_half) {
painter.add(outline(shape, Stroke::new(2.0, SELECTED)));
}
}
let model_size = |model: usize| {
definition
.model(model)
.map(|model| model.size_m)
.or(Some([0.01, 0.01]))
};
let pointer_m = response
.hover_pos()
.map(|pointer| to_metres(origin, pointer));
draw_tools::paint(painter, to_screen, origin, self.state, pointer_m);
let previews = generators::preview(self.state, definition.next_pixel_number())
.into_iter()
.chain(draw_tools::preview(self.edit, self.state));
for pixel in previews {
let shape = pixel_shape_of(&pixel, model_size(pixel.model), origin, min_half);
painter.add(outline(shape, Stroke::new(1.0, PREVIEW_COLOR)));
}
if let Some(Drag::Band(band)) = &self.state.drag {
draw_band(&mut self.overlay, &to_screen, band.rect());
}
painter.add(std::mem::take(&mut self.overlay).into_shape());
if let (Some(pixel), Some(pointer)) = (hovered, response.hover_pos()) {
self.tooltip(painter, pixel, to_screen * pointer);
}
}
fn grid(&self, origin: Pos2) -> Shape {
let to_screen = self.scene_transform;
let side = self.state.scene_side;
let scene = Rect::from_min_size(Pos2::ZERO, Vec2::splat(side));
let mut under = Shapes::default();
let line = |alpha| Stroke::new(0.5, Color32::from_white_alpha(alpha));
draw_adaptive_grid(
&mut under,
scene,
&to_screen,
&[(1.0, line(16)), (10.0, line(32)), (100.0, line(64))],
);
let axis = Stroke::new(1.0, Color32::from_white_alpha(90));
under.line_segment(
[
to_screen * pos2(0.0, origin.y),
to_screen * pos2(side, origin.y),
],
axis,
);
under.line_segment(
[
to_screen * pos2(origin.x, 0.0),
to_screen * pos2(origin.x, side),
],
axis,
);
let size = &self.edit.value.size_m;
let footprint = Rect::from_center_size(origin, vec2(size[0], size[1]) * UNITS_PER_METRE);
if footprint.area() > 0.0 {
let corners = [
footprint.left_top(),
footprint.right_top(),
footprint.right_bottom(),
footprint.left_bottom(),
footprint.left_top(),
]
.map(|corner| to_screen * corner);
under.add(Shape::dashed_line(
&corners,
Stroke::new(1.0, Color32::from_rgba_unmultiplied(255, 210, 90, 120)),
6.0,
4.0,
));
}
under.into_shape()
}
fn tooltip(&self, painter: &Painter, pixel: usize, pointer: Pos2) {
let definition = &*self.edit.value;
let Some(found) = definition.pixels.get(pixel) else {
return;
};
let first = definition.channel_offset(pixel).saturating_add(1);
let width = u16::try_from(found.channels.len()).unwrap_or(u16::MAX);
let last = first.saturating_add(width).saturating_sub(1);
let order = found
.color_order()
.map_or("custom".to_string(), |order| order.to_string());
let mut text = format!("{} · ch {first}–{last} · {order}", found.name);
if let Some(mode) = definition
.mode(self.state.mode)
.filter(|_| self.state.mode > 0)
&& let Some(group) = mode.group_of.get(pixel)
{
text.push_str(&format!(" · group {}", group + 1));
}
let at = pointer + vec2(14.0, 14.0);
let galley = painter.layout_no_wrap(text, FontId::proportional(12.0), Color32::WHITE);
let background = Rect::from_min_size(at, galley.size()).expand(4.0);
painter.rect_filled(background, 3.0, Color32::from_black_alpha(200));
painter.galley(at, galley, Color32::WHITE);
}
}