1use std::sync::Arc;
2
3use crate::{
4 emath::{Align2, Pos2, Rangef, Rect, Vec2},
5 layers::{LayerId, PaintList, ShapeIdx},
6 Color32, Context, FontId,
7};
8use epaint::{
9 text::{Fonts, Galley, LayoutJob},
10 CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke,
11};
12
13#[derive(Clone)]
17pub struct Painter {
18 ctx: Context,
20
21 layer_id: LayerId,
23
24 clip_rect: Rect,
27
28 fade_to_color: Option<Color32>,
31
32 opacity_factor: f32,
36}
37
38impl Painter {
39 pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
41 Self {
42 ctx,
43 layer_id,
44 clip_rect,
45 fade_to_color: None,
46 opacity_factor: 1.0,
47 }
48 }
49
50 #[must_use]
52 pub fn with_layer_id(self, layer_id: LayerId) -> Self {
53 Self {
54 ctx: self.ctx,
55 layer_id,
56 clip_rect: self.clip_rect,
57 fade_to_color: None,
58 opacity_factor: 1.0,
59 }
60 }
61
62 pub fn with_clip_rect(&self, rect: Rect) -> Self {
67 Self {
68 ctx: self.ctx.clone(),
69 layer_id: self.layer_id,
70 clip_rect: rect.intersect(self.clip_rect),
71 fade_to_color: self.fade_to_color,
72 opacity_factor: self.opacity_factor,
73 }
74 }
75
76 pub fn set_layer_id(&mut self, layer_id: LayerId) {
78 self.layer_id = layer_id;
79 }
80
81 pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
83 self.fade_to_color = fade_to_color;
84 }
85
86 pub fn set_opacity(&mut self, opacity: f32) {
93 if opacity.is_finite() {
94 self.opacity_factor = opacity.clamp(0.0, 1.0);
95 }
96 }
97
98 pub fn multiply_opacity(&mut self, opacity: f32) {
102 if opacity.is_finite() {
103 self.opacity_factor *= opacity.clamp(0.0, 1.0);
104 }
105 }
106
107 #[inline]
111 pub fn opacity(&self) -> f32 {
112 self.opacity_factor
113 }
114
115 pub(crate) fn is_visible(&self) -> bool {
116 self.fade_to_color != Some(Color32::TRANSPARENT)
117 }
118
119 pub(crate) fn set_invisible(&mut self) {
121 self.fade_to_color = Some(Color32::TRANSPARENT);
122 }
123}
124
125impl Painter {
127 #[inline]
129 pub fn ctx(&self) -> &Context {
130 &self.ctx
131 }
132
133 #[inline]
137 pub fn fonts<R>(&self, reader: impl FnOnce(&Fonts) -> R) -> R {
138 self.ctx.fonts(reader)
139 }
140
141 #[inline]
143 pub fn layer_id(&self) -> LayerId {
144 self.layer_id
145 }
146
147 #[inline]
150 pub fn clip_rect(&self) -> Rect {
151 self.clip_rect
152 }
153
154 #[inline]
157 pub fn set_clip_rect(&mut self, clip_rect: Rect) {
158 self.clip_rect = clip_rect;
159 }
160
161 #[inline]
163 pub fn round_to_pixel(&self, point: f32) -> f32 {
164 self.ctx().round_to_pixel(point)
165 }
166
167 #[inline]
169 pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
170 self.ctx().round_vec_to_pixels(vec)
171 }
172
173 #[inline]
175 pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
176 self.ctx().round_pos_to_pixels(pos)
177 }
178
179 #[inline]
181 pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
182 self.ctx().round_rect_to_pixels(rect)
183 }
184}
185
186impl Painter {
188 #[inline]
189 fn paint_list<R>(&self, writer: impl FnOnce(&mut PaintList) -> R) -> R {
190 self.ctx.graphics_mut(|g| writer(g.entry(self.layer_id)))
191 }
192
193 fn transform_shape(&self, shape: &mut Shape) {
194 if let Some(fade_to_color) = self.fade_to_color {
195 tint_shape_towards(shape, fade_to_color);
196 }
197 if self.opacity_factor < 1.0 {
198 multiply_opacity(shape, self.opacity_factor);
199 }
200 }
201
202 pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
206 if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 {
207 self.paint_list(|l| l.add(self.clip_rect, Shape::Noop))
208 } else {
209 let mut shape = shape.into();
210 self.transform_shape(&mut shape);
211 self.paint_list(|l| l.add(self.clip_rect, shape))
212 }
213 }
214
215 pub fn extend<I: IntoIterator<Item = Shape>>(&self, shapes: I) {
219 if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 {
220 return;
221 }
222 if self.fade_to_color.is_some() || self.opacity_factor < 1.0 {
223 let shapes = shapes.into_iter().map(|mut shape| {
224 self.transform_shape(&mut shape);
225 shape
226 });
227 self.paint_list(|l| l.extend(self.clip_rect, shapes));
228 } else {
229 self.paint_list(|l| l.extend(self.clip_rect, shapes));
230 }
231 }
232
233 pub fn set(&self, idx: ShapeIdx, shape: impl Into<Shape>) {
235 if self.fade_to_color == Some(Color32::TRANSPARENT) {
236 return;
237 }
238 let mut shape = shape.into();
239 self.transform_shape(&mut shape);
240 self.paint_list(|l| l.set(idx, self.clip_rect, shape));
241 }
242
243 pub fn for_each_shape(&self, mut reader: impl FnMut(&ClippedShape)) {
245 self.ctx.graphics(|g| {
246 if let Some(list) = g.get(self.layer_id) {
247 for c in list.all_entries() {
248 reader(c);
249 }
250 }
251 });
252 }
253}
254
255impl Painter {
257 #[allow(clippy::needless_pass_by_value)]
258 pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) {
259 self.rect(
260 rect,
261 0.0,
262 color.additive().linear_multiply(0.015),
263 (1.0, color),
264 );
265 self.text(
266 rect.min,
267 Align2::LEFT_TOP,
268 text.to_string(),
269 FontId::monospace(12.0),
270 color,
271 );
272 }
273
274 pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
275 let color = self.ctx.style().visuals.error_fg_color;
276 self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
277 }
278
279 #[allow(clippy::needless_pass_by_value)]
283 pub fn debug_text(
284 &self,
285 pos: Pos2,
286 anchor: Align2,
287 color: Color32,
288 text: impl ToString,
289 ) -> Rect {
290 let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(12.0), color);
291 let rect = anchor.anchor_size(pos, galley.size());
292 let frame_rect = rect.expand(2.0);
293
294 let is_text_bright = color.is_additive() || epaint::Rgba::from(color).intensity() > 0.5;
295 let bg_color = if is_text_bright {
296 Color32::from_black_alpha(150)
297 } else {
298 Color32::from_white_alpha(150)
299 };
300 self.add(Shape::rect_filled(frame_rect, 0.0, bg_color));
301 self.galley(rect.min, galley, color);
302 frame_rect
303 }
304}
305
306impl Painter {
308 pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<PathStroke>) -> ShapeIdx {
310 self.add(Shape::LineSegment {
311 points,
312 stroke: stroke.into(),
313 })
314 }
315
316 pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> ShapeIdx {
318 self.add(Shape::hline(x, y, stroke.into()))
319 }
320
321 pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> ShapeIdx {
323 self.add(Shape::vline(x, y, stroke.into()))
324 }
325
326 pub fn circle(
327 &self,
328 center: Pos2,
329 radius: f32,
330 fill_color: impl Into<Color32>,
331 stroke: impl Into<Stroke>,
332 ) -> ShapeIdx {
333 self.add(CircleShape {
334 center,
335 radius,
336 fill: fill_color.into(),
337 stroke: stroke.into(),
338 })
339 }
340
341 pub fn circle_filled(
342 &self,
343 center: Pos2,
344 radius: f32,
345 fill_color: impl Into<Color32>,
346 ) -> ShapeIdx {
347 self.add(CircleShape {
348 center,
349 radius,
350 fill: fill_color.into(),
351 stroke: Default::default(),
352 })
353 }
354
355 pub fn circle_stroke(&self, center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
356 self.add(CircleShape {
357 center,
358 radius,
359 fill: Default::default(),
360 stroke: stroke.into(),
361 })
362 }
363
364 pub fn rect(
365 &self,
366 rect: Rect,
367 rounding: impl Into<Rounding>,
368 fill_color: impl Into<Color32>,
369 stroke: impl Into<Stroke>,
370 ) -> ShapeIdx {
371 self.add(RectShape::new(rect, rounding, fill_color, stroke))
372 }
373
374 pub fn rect_filled(
375 &self,
376 rect: Rect,
377 rounding: impl Into<Rounding>,
378 fill_color: impl Into<Color32>,
379 ) -> ShapeIdx {
380 self.add(RectShape::filled(rect, rounding, fill_color))
381 }
382
383 pub fn rect_stroke(
384 &self,
385 rect: Rect,
386 rounding: impl Into<Rounding>,
387 stroke: impl Into<Stroke>,
388 ) -> ShapeIdx {
389 self.add(RectShape::stroke(rect, rounding, stroke))
390 }
391
392 pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: impl Into<Stroke>) {
394 use crate::emath::*;
395 let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
396 let tip_length = vec.length() / 4.0;
397 let tip = origin + vec;
398 let dir = vec.normalized();
399 let stroke = stroke.into();
400 self.line_segment([origin, tip], stroke);
401 self.line_segment([tip, tip - tip_length * (rot * dir)], stroke);
402 self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke);
403 }
404
405 pub fn image(
424 &self,
425 texture_id: epaint::TextureId,
426 rect: Rect,
427 uv: Rect,
428 tint: Color32,
429 ) -> ShapeIdx {
430 self.add(Shape::image(texture_id, rect, uv, tint))
431 }
432}
433
434impl Painter {
436 #[allow(clippy::needless_pass_by_value)]
445 pub fn text(
446 &self,
447 pos: Pos2,
448 anchor: Align2,
449 text: impl ToString,
450 font_id: FontId,
451 text_color: Color32,
452 ) -> Rect {
453 let galley = self.layout_no_wrap(text.to_string(), font_id, text_color);
454 let rect = anchor.anchor_size(pos, galley.size());
455 self.galley(rect.min, galley, text_color);
456 rect
457 }
458
459 #[inline]
463 #[must_use]
464 pub fn layout(
465 &self,
466 text: String,
467 font_id: FontId,
468 color: crate::Color32,
469 wrap_width: f32,
470 ) -> Arc<Galley> {
471 self.fonts(|f| f.layout(text, font_id, color, wrap_width))
472 }
473
474 #[inline]
478 #[must_use]
479 pub fn layout_no_wrap(
480 &self,
481 text: String,
482 font_id: FontId,
483 color: crate::Color32,
484 ) -> Arc<Galley> {
485 self.fonts(|f| f.layout(text, font_id, color, f32::INFINITY))
486 }
487
488 #[inline]
492 #[must_use]
493 pub fn layout_job(&self, layout_job: LayoutJob) -> Arc<Galley> {
494 self.fonts(|f| f.layout_job(layout_job))
495 }
496
497 #[inline]
505 pub fn galley(&self, pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) {
506 if !galley.is_empty() {
507 self.add(Shape::galley(pos, galley, fallback_color));
508 }
509 }
510
511 #[inline]
517 pub fn galley_with_override_text_color(
518 &self,
519 pos: Pos2,
520 galley: Arc<Galley>,
521 text_color: Color32,
522 ) {
523 if !galley.is_empty() {
524 self.add(Shape::galley_with_override_text_color(
525 pos, galley, text_color,
526 ));
527 }
528 }
529
530 #[deprecated = "Use `Painter::galley` or `Painter::galley_with_override_text_color` instead"]
531 #[inline]
532 pub fn galley_with_color(&self, pos: Pos2, galley: Arc<Galley>, text_color: Color32) {
533 if !galley.is_empty() {
534 self.add(Shape::galley_with_override_text_color(
535 pos, galley, text_color,
536 ));
537 }
538 }
539}
540
541fn tint_shape_towards(shape: &mut Shape, target: Color32) {
542 epaint::shape_transform::adjust_colors(shape, move |color| {
543 if *color != Color32::PLACEHOLDER {
544 *color = crate::ecolor::tint_color_towards(*color, target);
545 }
546 });
547}
548
549fn multiply_opacity(shape: &mut Shape, opacity: f32) {
550 epaint::shape_transform::adjust_colors(shape, move |color| {
551 if *color != Color32::PLACEHOLDER {
552 *color = color.gamma_multiply(opacity);
553 }
554 });
555}