use dais_core::state::{PointerStyle, PresentationState};
use dais_document::typst_renderer::TextBoxRenderCache;
use crate::widgets::TextBoxTextureCache;
pub fn draw_overlays(
ui: &mut egui::Ui,
viewport_rect: egui::Rect,
image_rect: egui::Rect,
state: &PresentationState,
tb_cache: &mut TextBoxRenderCache,
texture_cache: &mut TextBoxTextureCache,
draw_text_boxes: bool,
) {
let page_ink = state.current_page_ink();
if !page_ink.is_empty() {
crate::widgets::draw_ink_strokes(ui, image_rect, page_ink);
}
let page_text_boxes = state.current_page_text_boxes();
if draw_text_boxes && !page_text_boxes.is_empty() {
let _ = crate::widgets::draw_text_boxes(
ui,
page_text_boxes,
None,
None,
false,
image_rect,
tb_cache,
texture_cache,
);
}
if state.laser_active
&& let Some((px, py)) = state.pointer_position
{
let appearance = state.current_pointer_appearance();
draw_laser_overlay(
ui,
image_rect,
px,
py,
appearance.color,
appearance.size,
state.pointer_style,
);
}
if state.spotlight_active
&& let Some((sx, sy)) = state.spotlight_position
{
draw_spotlight_overlay(
ui,
image_rect,
sx,
sy,
state.spotlight_radius,
state.spotlight_dim_opacity,
);
}
if state.blacked_out {
ui.painter().rect_filled(viewport_rect, 0.0, egui::Color32::BLACK);
}
if state.whiteboard_active {
ui.painter().rect_filled(viewport_rect, 0.0, egui::Color32::WHITE);
if !state.whiteboard_strokes.is_empty() {
crate::widgets::draw_ink_strokes(ui, image_rect, &state.whiteboard_strokes);
}
}
}
pub fn draw_laser_overlay(
ui: &mut egui::Ui,
image_rect: egui::Rect,
nx: f32,
ny: f32,
color: [u8; 4],
size: f32,
style: PointerStyle,
) {
let pos = denormalize(image_rect, nx, ny);
let painter = ui.painter();
let color = color32_from_rgba(color);
let size = size.clamp(2.0, 96.0);
let glow = egui::Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 72);
match style {
PointerStyle::Dot => {
painter.circle_filled(pos, (size * 0.95).max(4.0), glow);
painter.circle_filled(pos, (size * 0.45).max(2.0), color);
}
PointerStyle::Ring => {
let stroke = egui::Stroke::new((size * 0.14).max(1.8), color);
painter.circle_filled(pos, (size * 0.85).max(5.0), glow);
painter.circle_stroke(pos, (size * 0.55).max(4.0), stroke);
}
PointerStyle::Bullseye => {
let stroke = egui::Stroke::new((size * 0.13).max(1.6), color);
painter.circle_filled(pos, (size * 0.9).max(5.0), glow);
painter.circle_stroke(pos, (size * 0.6).max(4.0), stroke);
painter.circle_filled(pos, (size * 0.13).max(1.4), color);
}
PointerStyle::Highlight => {
let fill = egui::Color32::from_rgba_unmultiplied(
color.r(),
color.g(),
color.b(),
color.a().min(96),
);
let stroke = egui::Stroke::new(
(size * 0.08).max(1.2),
egui::Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 180),
);
painter.circle_filled(pos, (size * 0.75).max(5.0), fill);
painter.circle_stroke(pos, (size * 0.75).max(5.0), stroke);
}
PointerStyle::Crosshair => {
let arm = (size * 1.2).max(8.0);
let gap = (size * 0.35).max(3.0);
let stroke = egui::Stroke::new((size * 0.16).max(1.5), color);
painter.circle_stroke(pos, (size * 0.45).max(3.0), stroke);
painter
.line_segment([pos + egui::vec2(-arm, 0.0), pos + egui::vec2(-gap, 0.0)], stroke);
painter.line_segment([pos + egui::vec2(gap, 0.0), pos + egui::vec2(arm, 0.0)], stroke);
painter
.line_segment([pos + egui::vec2(0.0, -arm), pos + egui::vec2(0.0, -gap)], stroke);
painter.line_segment([pos + egui::vec2(0.0, gap), pos + egui::vec2(0.0, arm)], stroke);
painter.circle_filled(pos, (size * 0.18).max(1.5), color);
}
PointerStyle::Arrow => {
let head_len = (size * 0.70).max(6.0);
let head_half_width = (size * 0.24).max(2.2);
let tail_len = (size * 0.31).max(3.0);
let tail_start = head_len * 0.50;
let angle = 60.0_f32.to_radians();
let dir = egui::vec2(angle.cos(), angle.sin());
let perp = egui::vec2(-dir.y, dir.x);
let base_center = pos + dir * head_len;
let left = base_center + perp * head_half_width;
let right = base_center - perp * head_half_width;
let tail_join = pos + dir * tail_start;
let tail_end = pos + dir * (head_len + tail_len);
let stroke = egui::Stroke::new((size * 0.145).max(1.7), color);
painter.line_segment([tail_join, tail_end], stroke);
painter.add(egui::Shape::convex_polygon(
vec![pos, left, right],
color,
egui::Stroke::NONE,
));
painter.circle_filled(pos, (size * 0.16).max(1.5), glow);
}
}
}
pub fn draw_spotlight_overlay(
ui: &mut egui::Ui,
image_rect: egui::Rect,
nx: f32,
ny: f32,
radius: f32,
dim_opacity: f32,
) {
let half_size = radius.clamp(16.0, image_rect.width().min(image_rect.height()) * 0.45);
let center = denormalize(image_rect, nx, ny);
let painter = ui.painter_at(image_rect);
let dim_color =
egui::Color32::from_rgba_unmultiplied(0, 0, 0, dim_opacity_to_alpha(dim_opacity));
let hole_rect =
egui::Rect::from_center_size(center, egui::vec2(half_size * 2.0, half_size * 2.0))
.intersect(image_rect);
if hole_rect.top() > image_rect.top() {
painter.rect_filled(
egui::Rect::from_min_max(
image_rect.left_top(),
egui::pos2(image_rect.right(), hole_rect.top()),
),
0.0,
dim_color,
);
}
if hole_rect.bottom() < image_rect.bottom() {
painter.rect_filled(
egui::Rect::from_min_max(
egui::pos2(image_rect.left(), hole_rect.bottom()),
image_rect.right_bottom(),
),
0.0,
dim_color,
);
}
if hole_rect.left() > image_rect.left() {
painter.rect_filled(
egui::Rect::from_min_max(
egui::pos2(image_rect.left(), hole_rect.top()),
egui::pos2(hole_rect.left(), hole_rect.bottom()),
),
0.0,
dim_color,
);
}
if hole_rect.right() < image_rect.right() {
painter.rect_filled(
egui::Rect::from_min_max(
egui::pos2(hole_rect.right(), hole_rect.top()),
egui::pos2(image_rect.right(), hole_rect.bottom()),
),
0.0,
dim_color,
);
}
painter.rect_stroke(
hole_rect,
0.0,
egui::Stroke::new(2.0, egui::Color32::from_rgba_unmultiplied(255, 255, 255, 100)),
egui::StrokeKind::Outside,
);
}
fn color32_from_rgba(color: [u8; 4]) -> egui::Color32 {
egui::Color32::from_rgba_unmultiplied(color[0], color[1], color[2], color[3])
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn dim_opacity_to_alpha(dim_opacity: f32) -> u8 {
(dim_opacity.clamp(0.0, 1.0) * 255.0).round() as u8
}
pub fn draw_zoom_indicator(
ui: &mut egui::Ui,
image_rect: egui::Rect,
center: (f32, f32),
factor: f32,
) {
let pos = denormalize(image_rect, center.0, center.1);
let painter = ui.painter();
let half_w = image_rect.width() / (factor * 2.0);
let half_h = image_rect.height() / (factor * 2.0);
let zoom_rect = egui::Rect::from_center_size(pos, egui::vec2(half_w * 2.0, half_h * 2.0));
painter.rect_stroke(
zoom_rect,
0.0,
egui::Stroke::new(2.0, egui::Color32::YELLOW),
egui::StrokeKind::Outside,
);
painter.text(
zoom_rect.right_top() + egui::vec2(4.0, 0.0),
egui::Align2::LEFT_TOP,
format!("{factor:.1}x"),
egui::FontId::proportional(12.0),
egui::Color32::YELLOW,
);
}
fn denormalize(rect: egui::Rect, nx: f32, ny: f32) -> egui::Pos2 {
egui::pos2(rect.min.x + nx * rect.width(), rect.min.y + ny * rect.height())
}