Skip to main content

gpui_fps/
overlay.rs

1use gpui::{
2    Anchor, App, Entity, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window, div,
3    prelude::FluentBuilder as _, px,
4};
5use std::time::Duration;
6
7use crate::monitor::FpsMonitor;
8
9/// Distance from the edges the HUD is pinned to.
10const MARGIN: Pixels = px(12.);
11
12/// Pins an [`FpsMonitor`] to an edge or corner of its parent, the way a game
13/// overlays its frame counter.
14///
15/// Most applications want [`fps_monitor`](crate::fps_monitor) instead, which
16/// creates and reuses the monitor for you. Reach for this when you already hold
17/// a configured [`FpsMonitor`].
18///
19/// The overlay positions itself absolutely, so **the parent must be
20/// `relative()`**:
21///
22/// ```no_run
23/// # use gpui::*;
24/// # use gpui_fps::{FpsMonitor, FpsOverlay};
25/// # fn example(monitor: &Entity<FpsMonitor>, content: impl IntoElement) -> impl IntoElement {
26/// div()
27///     .relative()
28///     .size_full()
29///     .child(content)
30///     .child(FpsOverlay::new(monitor).anchor(Anchor::BottomLeft))
31/// # }
32/// ```
33#[derive(IntoElement)]
34pub struct FpsOverlay {
35    monitor: Entity<FpsMonitor>,
36    anchor: Anchor,
37    frame_budget: Option<Duration>,
38    continuous: Option<bool>,
39}
40
41impl FpsOverlay {
42    pub fn new(monitor: &Entity<FpsMonitor>) -> Self {
43        Self {
44            monitor: monitor.clone(),
45            anchor: Anchor::TopRight,
46            frame_budget: None,
47            continuous: None,
48        }
49    }
50
51    /// Where in the parent the HUD sits. Defaults to [`Anchor::TopRight`].
52    pub fn anchor(mut self, anchor: Anchor) -> Self {
53        self.anchor = anchor;
54        self
55    }
56
57    /// The per-frame budget used for chart grading and its vertical scale.
58    pub fn frame_budget(mut self, budget: Duration) -> Self {
59        self.frame_budget = Some(budget);
60        self
61    }
62
63    /// Whether the HUD requests another animation frame after every render.
64    /// Defaults to the monitor's current setting (`true` on first use).
65    pub fn continuous(mut self, continuous: bool) -> Self {
66        self.continuous = Some(continuous);
67        self
68    }
69}
70
71impl RenderOnce for FpsOverlay {
72    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
73        if self.frame_budget.is_some() || self.continuous.is_some() {
74            self.monitor.update(cx, |monitor, _| {
75                if let Some(budget) = self.frame_budget {
76                    monitor.set_frame_budget(budget);
77                }
78                if let Some(continuous) = self.continuous {
79                    monitor.set_continuous(continuous);
80                }
81            });
82        }
83        let margin = MARGIN;
84
85        // Corners are placed by their own two offsets so the overlay stays the
86        // size of the HUD. The centered anchors need a strip to center within,
87        // but it is only stretched along the one axis that needs it, keeping
88        // the area laid over the content as small as possible.
89        div()
90            .absolute()
91            .flex()
92            .map(|this| match self.anchor {
93                Anchor::TopLeft => this.top(margin).left(margin),
94                Anchor::TopRight => this.top(margin).right(margin),
95                Anchor::BottomLeft => this.bottom(margin).left(margin),
96                Anchor::BottomRight => this.bottom(margin).right(margin),
97                Anchor::TopCenter => this.top(margin).left_0().right_0().justify_center(),
98                Anchor::BottomCenter => this.bottom(margin).left_0().right_0().justify_center(),
99                Anchor::LeftCenter => this.left(margin).top_0().bottom_0().items_center(),
100                Anchor::RightCenter => this.right(margin).top_0().bottom_0().items_center(),
101            })
102            .child(self.monitor)
103    }
104}