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}
39
40impl FpsOverlay {
41 pub fn new(monitor: &Entity<FpsMonitor>) -> Self {
42 Self {
43 monitor: monitor.clone(),
44 anchor: Anchor::TopRight,
45 frame_budget: None,
46 }
47 }
48
49 /// Where in the parent the HUD sits. Defaults to [`Anchor::TopRight`].
50 pub fn anchor(mut self, anchor: Anchor) -> Self {
51 self.anchor = anchor;
52 self
53 }
54
55 /// The per-frame budget used for chart grading and its vertical scale.
56 /// Set, it holds regardless of the display; left alone, the HUD grades
57 /// against one refresh of the panel the window is on.
58 pub fn frame_budget(mut self, budget: Duration) -> Self {
59 self.frame_budget = Some(budget);
60 self
61 }
62}
63
64impl RenderOnce for FpsOverlay {
65 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
66 if let Some(budget) = self.frame_budget {
67 self.monitor
68 .update(cx, |monitor, _| monitor.set_frame_budget(budget));
69 }
70 let margin = MARGIN;
71
72 // Corners are placed by their own two offsets so the overlay stays the
73 // size of the HUD. The centered anchors need a strip to center within,
74 // but it is only stretched along the one axis that needs it, keeping
75 // the area laid over the content as small as possible.
76 div()
77 .absolute()
78 .flex()
79 .map(|this| match self.anchor {
80 Anchor::TopLeft => this.top(margin).left(margin),
81 Anchor::TopRight => this.top(margin).right(margin),
82 Anchor::BottomLeft => this.bottom(margin).left(margin),
83 Anchor::BottomRight => this.bottom(margin).right(margin),
84 Anchor::TopCenter => this.top(margin).left_0().right_0().justify_center(),
85 Anchor::BottomCenter => this.bottom(margin).left_0().right_0().justify_center(),
86 Anchor::LeftCenter => this.left(margin).top_0().bottom_0().items_center(),
87 Anchor::RightCenter => this.right(margin).top_0().bottom_0().items_center(),
88 })
89 .child(self.monitor)
90 }
91}