egui/
painter.rs

1use std::sync::Arc;
2
3use emath::GuiRounding as _;
4use epaint::{
5    CircleShape, ClippedShape, CornerRadius, PathStroke, RectShape, Shape, Stroke, StrokeKind,
6    text::{FontsView, Galley, LayoutJob},
7};
8
9use crate::{
10    Color32, Context, FontId,
11    emath::{Align2, Pos2, Rangef, Rect, Vec2},
12    layers::{LayerId, PaintList, ShapeIdx},
13};
14
15/// Helper to paint shapes and text to a specific region on a specific layer.
16///
17/// All coordinates are screen coordinates in the unit points (one point can consist of many physical pixels).
18///
19/// A [`Painter`] never outlive a single frame/pass.
20#[derive(Clone)]
21pub struct Painter {
22    /// Source of fonts and destination of shapes
23    ctx: Context,
24
25    /// For quick access, without having to go via [`Context`].
26    pixels_per_point: f32,
27
28    /// Where we paint
29    layer_id: LayerId,
30
31    /// Everything painted in this [`Painter`] will be clipped against this.
32    /// This means nothing outside of this rectangle will be visible on screen.
33    clip_rect: Rect,
34
35    /// If set, all shapes will have their colors modified to be closer to this.
36    /// This is used to implement grayed out interfaces.
37    fade_to_color: Option<Color32>,
38
39    /// If set, all shapes will have their colors modified with [`Color32::gamma_multiply`] with
40    /// this value as the factor.
41    /// This is used to make interfaces semi-transparent.
42    opacity_factor: f32,
43}
44
45impl Painter {
46    /// Create a painter to a specific layer within a certain clip rectangle.
47    pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
48        let pixels_per_point = ctx.pixels_per_point();
49        Self {
50            ctx,
51            pixels_per_point,
52            layer_id,
53            clip_rect,
54            fade_to_color: None,
55            opacity_factor: 1.0,
56        }
57    }
58
59    /// Redirect where you are painting.
60    #[must_use]
61    #[inline]
62    pub fn with_layer_id(mut self, layer_id: LayerId) -> Self {
63        self.layer_id = layer_id;
64        self
65    }
66
67    /// Create a painter for a sub-region of this [`Painter`].
68    ///
69    /// The clip-rect of the returned [`Painter`] will be the intersection
70    /// of the given rectangle and the `clip_rect()` of the parent [`Painter`].
71    pub fn with_clip_rect(&self, rect: Rect) -> Self {
72        let mut new_self = self.clone();
73        new_self.clip_rect = rect.intersect(self.clip_rect);
74        new_self
75    }
76
77    /// Redirect where you are painting.
78    ///
79    /// It is undefined behavior to change the [`LayerId`]
80    /// of [`crate::Ui::painter`].
81    pub fn set_layer_id(&mut self, layer_id: LayerId) {
82        self.layer_id = layer_id;
83    }
84
85    /// If set, colors will be modified to look like this
86    #[deprecated = "Use `multiply_opacity` instead"]
87    pub fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
88        self.fade_to_color = fade_to_color;
89    }
90
91    /// Set the opacity (alpha multiplier) of everything painted by this painter from this point forward.
92    ///
93    /// `opacity` must be between 0.0 and 1.0, where 0.0 means fully transparent (i.e., invisible)
94    /// and 1.0 means fully opaque.
95    ///
96    /// See also: [`Self::opacity`] and [`Self::multiply_opacity`].
97    pub fn set_opacity(&mut self, opacity: f32) {
98        if opacity.is_finite() {
99            self.opacity_factor = opacity.clamp(0.0, 1.0);
100        }
101    }
102
103    /// Like [`Self::set_opacity`], but multiplies the given value with the current opacity.
104    ///
105    /// See also: [`Self::set_opacity`] and [`Self::opacity`].
106    pub fn multiply_opacity(&mut self, opacity: f32) {
107        if opacity.is_finite() {
108            self.opacity_factor *= opacity.clamp(0.0, 1.0);
109        }
110    }
111
112    /// Read the current opacity of the underlying painter.
113    ///
114    /// See also: [`Self::set_opacity`] and [`Self::multiply_opacity`].
115    #[inline]
116    pub fn opacity(&self) -> f32 {
117        self.opacity_factor
118    }
119
120    /// If `false`, nothing you paint will show up.
121    ///
122    /// Also checks [`Context::will_discard`].
123    pub fn is_visible(&self) -> bool {
124        self.fade_to_color != Some(Color32::TRANSPARENT) && !self.ctx.will_discard()
125    }
126
127    /// If `false`, nothing added to the painter will be visible
128    pub fn set_invisible(&mut self) {
129        self.fade_to_color = Some(Color32::TRANSPARENT);
130    }
131
132    /// Get a reference to the parent [`Context`].
133    #[inline]
134    pub fn ctx(&self) -> &Context {
135        &self.ctx
136    }
137
138    /// Number of physical pixels for each logical UI point.
139    #[inline]
140    pub fn pixels_per_point(&self) -> f32 {
141        self.pixels_per_point
142    }
143
144    /// Read-only access to the shared [`FontsView`].
145    ///
146    /// See [`Context`] documentation for how locks work.
147    #[inline]
148    pub fn fonts<R>(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R {
149        self.ctx.fonts(reader)
150    }
151
152    /// Read-write access to the shared [`FontsView`].
153    ///
154    /// See [`Context`] documentation for how locks work.
155    #[inline]
156    pub fn fonts_mut<R>(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R {
157        self.ctx.fonts_mut(reader)
158    }
159
160    /// Where we paint
161    #[inline]
162    pub fn layer_id(&self) -> LayerId {
163        self.layer_id
164    }
165
166    /// Everything painted in this [`Painter`] will be clipped against this.
167    /// This means nothing outside of this rectangle will be visible on screen.
168    #[inline]
169    pub fn clip_rect(&self) -> Rect {
170        self.clip_rect
171    }
172
173    /// Constrain the rectangle in which we can paint.
174    ///
175    /// Short for `painter.set_clip_rect(painter.clip_rect().intersect(new_clip_rect))`.
176    ///
177    /// See also: [`Self::clip_rect`] and [`Self::set_clip_rect`].
178    #[inline]
179    pub fn shrink_clip_rect(&mut self, new_clip_rect: Rect) {
180        self.clip_rect = self.clip_rect.intersect(new_clip_rect);
181    }
182
183    /// Everything painted in this [`Painter`] will be clipped against this.
184    /// This means nothing outside of this rectangle will be visible on screen.
185    ///
186    /// Warning: growing the clip rect might cause unexpected results!
187    /// When in doubt, use [`Self::shrink_clip_rect`] instead.
188    #[inline]
189    pub fn set_clip_rect(&mut self, clip_rect: Rect) {
190        self.clip_rect = clip_rect;
191    }
192
193    /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
194    #[inline]
195    pub fn round_to_pixel_center(&self, point: f32) -> f32 {
196        point.round_to_pixel_center(self.pixels_per_point())
197    }
198
199    /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels).
200    #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
201    #[inline]
202    pub fn round_pos_to_pixel_center(&self, pos: Pos2) -> Pos2 {
203        pos.round_to_pixel_center(self.pixels_per_point())
204    }
205
206    /// Useful for pixel-perfect rendering of filled shapes.
207    #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
208    #[inline]
209    pub fn round_to_pixel(&self, point: f32) -> f32 {
210        point.round_to_pixels(self.pixels_per_point())
211    }
212
213    /// Useful for pixel-perfect rendering.
214    #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
215    #[inline]
216    pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
217        vec.round_to_pixels(self.pixels_per_point())
218    }
219
220    /// Useful for pixel-perfect rendering.
221    #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
222    #[inline]
223    pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
224        pos.round_to_pixels(self.pixels_per_point())
225    }
226
227    /// Useful for pixel-perfect rendering.
228    #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"]
229    #[inline]
230    pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
231        rect.round_to_pixels(self.pixels_per_point())
232    }
233}
234
235/// ## Low level
236impl Painter {
237    #[inline]
238    fn paint_list<R>(&self, writer: impl FnOnce(&mut PaintList) -> R) -> R {
239        self.ctx.graphics_mut(|g| writer(g.entry(self.layer_id)))
240    }
241
242    fn transform_shape(&self, shape: &mut Shape) {
243        if let Some(fade_to_color) = self.fade_to_color {
244            tint_shape_towards(shape, fade_to_color);
245        }
246        if self.opacity_factor < 1.0 {
247            multiply_opacity(shape, self.opacity_factor);
248        }
249    }
250
251    /// It is up to the caller to make sure there is room for this.
252    /// Can be used for free painting.
253    /// NOTE: all coordinates are screen coordinates!
254    pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
255        if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 {
256            self.paint_list(|l| l.add(self.clip_rect, Shape::Noop))
257        } else {
258            let mut shape = shape.into();
259            self.transform_shape(&mut shape);
260            self.paint_list(|l| l.add(self.clip_rect, shape))
261        }
262    }
263
264    /// Add many shapes at once.
265    ///
266    /// Calling this once is generally faster than calling [`Self::add`] multiple times.
267    pub fn extend<I: IntoIterator<Item = Shape>>(&self, shapes: I) {
268        if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 {
269            return;
270        }
271        if self.fade_to_color.is_some() || self.opacity_factor < 1.0 {
272            let shapes = shapes.into_iter().map(|mut shape| {
273                self.transform_shape(&mut shape);
274                shape
275            });
276            self.paint_list(|l| l.extend(self.clip_rect, shapes));
277        } else {
278            self.paint_list(|l| l.extend(self.clip_rect, shapes));
279        }
280    }
281
282    /// Modify an existing [`Shape`].
283    pub fn set(&self, idx: ShapeIdx, shape: impl Into<Shape>) {
284        if self.fade_to_color == Some(Color32::TRANSPARENT) {
285            return;
286        }
287        let mut shape = shape.into();
288        self.transform_shape(&mut shape);
289        self.paint_list(|l| l.set(idx, self.clip_rect, shape));
290    }
291
292    /// Access all shapes added this frame.
293    pub fn for_each_shape(&self, mut reader: impl FnMut(&ClippedShape)) {
294        self.ctx.graphics(|g| {
295            if let Some(list) = g.get(self.layer_id) {
296                for c in list.all_entries() {
297                    reader(c);
298                }
299            }
300        });
301    }
302}
303
304/// ## Debug painting
305impl Painter {
306    #[expect(clippy::needless_pass_by_value)]
307    pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) {
308        self.rect(
309            rect,
310            0.0,
311            color.additive().linear_multiply(0.015),
312            (1.0, color),
313            StrokeKind::Outside,
314        );
315        self.text(
316            rect.min,
317            Align2::LEFT_TOP,
318            text.to_string(),
319            FontId::monospace(12.0),
320            color,
321        );
322    }
323
324    pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
325        let color = self.ctx.style().visuals.error_fg_color;
326        self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
327    }
328
329    /// Text with a background.
330    ///
331    /// See also [`Context::debug_text`].
332    #[expect(clippy::needless_pass_by_value)]
333    pub fn debug_text(
334        &self,
335        pos: Pos2,
336        anchor: Align2,
337        color: Color32,
338        text: impl ToString,
339    ) -> Rect {
340        let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(12.0), color);
341        let rect = anchor.anchor_size(pos, galley.size());
342        let frame_rect = rect.expand(2.0);
343
344        let is_text_bright = color.is_additive() || epaint::Rgba::from(color).intensity() > 0.5;
345        let bg_color = if is_text_bright {
346            Color32::from_black_alpha(150)
347        } else {
348            Color32::from_white_alpha(150)
349        };
350        self.add(Shape::rect_filled(frame_rect, 0.0, bg_color));
351        self.galley(rect.min, galley, color);
352        frame_rect
353    }
354}
355
356/// # Paint different primitives
357impl Painter {
358    /// Paints a line from the first point to the second.
359    pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) -> ShapeIdx {
360        self.add(Shape::LineSegment {
361            points,
362            stroke: stroke.into(),
363        })
364    }
365
366    /// Paints a line connecting the points.
367    /// NOTE: all coordinates are screen coordinates!
368    pub fn line(&self, points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> ShapeIdx {
369        self.add(Shape::line(points, stroke))
370    }
371
372    /// Paints a horizontal line.
373    pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
374        self.add(Shape::hline(x, y, stroke))
375    }
376
377    /// Paints a vertical line.
378    pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> ShapeIdx {
379        self.add(Shape::vline(x, y, stroke))
380    }
381
382    pub fn circle(
383        &self,
384        center: Pos2,
385        radius: f32,
386        fill_color: impl Into<Color32>,
387        stroke: impl Into<Stroke>,
388    ) -> ShapeIdx {
389        self.add(CircleShape {
390            center,
391            radius,
392            fill: fill_color.into(),
393            stroke: stroke.into(),
394        })
395    }
396
397    pub fn circle_filled(
398        &self,
399        center: Pos2,
400        radius: f32,
401        fill_color: impl Into<Color32>,
402    ) -> ShapeIdx {
403        self.add(CircleShape {
404            center,
405            radius,
406            fill: fill_color.into(),
407            stroke: Default::default(),
408        })
409    }
410
411    pub fn circle_stroke(&self, center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
412        self.add(CircleShape {
413            center,
414            radius,
415            fill: Default::default(),
416            stroke: stroke.into(),
417        })
418    }
419
420    /// See also [`Self::rect_filled`] and [`Self::rect_stroke`].
421    pub fn rect(
422        &self,
423        rect: Rect,
424        corner_radius: impl Into<CornerRadius>,
425        fill_color: impl Into<Color32>,
426        stroke: impl Into<Stroke>,
427        stroke_kind: StrokeKind,
428    ) -> ShapeIdx {
429        self.add(RectShape::new(
430            rect,
431            corner_radius,
432            fill_color,
433            stroke,
434            stroke_kind,
435        ))
436    }
437
438    pub fn rect_filled(
439        &self,
440        rect: Rect,
441        corner_radius: impl Into<CornerRadius>,
442        fill_color: impl Into<Color32>,
443    ) -> ShapeIdx {
444        self.add(RectShape::filled(rect, corner_radius, fill_color))
445    }
446
447    pub fn rect_stroke(
448        &self,
449        rect: Rect,
450        corner_radius: impl Into<CornerRadius>,
451        stroke: impl Into<Stroke>,
452        stroke_kind: StrokeKind,
453    ) -> ShapeIdx {
454        self.add(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
455    }
456
457    /// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
458    pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: impl Into<Stroke>) {
459        use crate::emath::Rot2;
460        let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
461        let tip_length = vec.length() / 4.0;
462        let tip = origin + vec;
463        let dir = vec.normalized();
464        let stroke = stroke.into();
465        self.line_segment([origin, tip], stroke);
466        self.line_segment([tip, tip - tip_length * (rot * dir)], stroke);
467        self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke);
468    }
469
470    /// An image at the given position.
471    ///
472    /// `uv` should normally be `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`
473    /// unless you want to crop or flip the image.
474    ///
475    /// `tint` is a color multiplier. Use [`Color32::WHITE`] if you don't want to tint the image.
476    ///
477    /// Usually it is easier to use [`crate::Image::paint_at`] instead:
478    ///
479    /// ```
480    /// # egui::__run_test_ui(|ui| {
481    /// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
482    /// egui::Image::new(egui::include_image!("../assets/ferris.png"))
483    ///     .corner_radius(5)
484    ///     .tint(egui::Color32::LIGHT_BLUE)
485    ///     .paint_at(ui, rect);
486    /// # });
487    /// ```
488    pub fn image(
489        &self,
490        texture_id: epaint::TextureId,
491        rect: Rect,
492        uv: Rect,
493        tint: Color32,
494    ) -> ShapeIdx {
495        self.add(Shape::image(texture_id, rect, uv, tint))
496    }
497}
498
499/// ## Text
500impl Painter {
501    /// Lay out and paint some text.
502    ///
503    /// To center the text at the given position, use `Align2::CENTER_CENTER`.
504    ///
505    /// To find out the size of text before painting it, use
506    /// [`Self::layout`] or [`Self::layout_no_wrap`].
507    ///
508    /// Returns where the text ended up.
509    #[expect(clippy::needless_pass_by_value)]
510    pub fn text(
511        &self,
512        pos: Pos2,
513        anchor: Align2,
514        text: impl ToString,
515        font_id: FontId,
516        text_color: Color32,
517    ) -> Rect {
518        let galley = self.layout_no_wrap(text.to_string(), font_id, text_color);
519        let rect = anchor.anchor_size(pos, galley.size());
520        self.galley(rect.min, galley, text_color);
521        rect
522    }
523
524    /// Will wrap text at the given width and line break at `\n`.
525    ///
526    /// Paint the results with [`Self::galley`].
527    #[inline]
528    #[must_use]
529    pub fn layout(
530        &self,
531        text: String,
532        font_id: FontId,
533        color: crate::Color32,
534        wrap_width: f32,
535    ) -> Arc<Galley> {
536        self.fonts_mut(|f| f.layout(text, font_id, color, wrap_width))
537    }
538
539    /// Will line break at `\n`.
540    ///
541    /// Paint the results with [`Self::galley`].
542    #[inline]
543    #[must_use]
544    pub fn layout_no_wrap(
545        &self,
546        text: String,
547        font_id: FontId,
548        color: crate::Color32,
549    ) -> Arc<Galley> {
550        self.fonts_mut(|f| f.layout(text, font_id, color, f32::INFINITY))
551    }
552
553    /// Lay out this text layut job in a galley.
554    ///
555    /// Paint the results with [`Self::galley`].
556    #[inline]
557    #[must_use]
558    pub fn layout_job(&self, layout_job: LayoutJob) -> Arc<Galley> {
559        self.fonts_mut(|f| f.layout_job(layout_job))
560    }
561
562    /// Paint text that has already been laid out in a [`Galley`].
563    ///
564    /// You can create the [`Galley`] with [`Self::layout`] or [`Self::layout_job`].
565    ///
566    /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color.
567    ///
568    /// Any non-placeholder color in the galley takes precedence over this fallback color.
569    #[inline]
570    pub fn galley(&self, pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) {
571        if !galley.is_empty() {
572            self.add(Shape::galley(pos, galley, fallback_color));
573        }
574    }
575
576    /// Paint text that has already been laid out in a [`Galley`].
577    ///
578    /// You can create the [`Galley`] with [`Self::layout`].
579    ///
580    /// All text color in the [`Galley`] will be replaced with the given color.
581    #[inline]
582    pub fn galley_with_override_text_color(
583        &self,
584        pos: Pos2,
585        galley: Arc<Galley>,
586        text_color: Color32,
587    ) {
588        if !galley.is_empty() {
589            self.add(Shape::galley_with_override_text_color(
590                pos, galley, text_color,
591            ));
592        }
593    }
594}
595
596fn tint_shape_towards(shape: &mut Shape, target: Color32) {
597    epaint::shape_transform::adjust_colors(shape, move |color| {
598        if *color != Color32::PLACEHOLDER {
599            *color = crate::ecolor::tint_color_towards(*color, target);
600        }
601    });
602}
603
604fn multiply_opacity(shape: &mut Shape, opacity: f32) {
605    epaint::shape_transform::adjust_colors(shape, move |color| {
606        if *color != Color32::PLACEHOLDER {
607            *color = color.gamma_multiply(opacity);
608        }
609    });
610}