Skip to main content

cu_bevymon/
focus.rs

1use bevy::math::Rect;
2use bevy::prelude::*;
3use bevy::ui::{ComputedStackIndex, UiGlobalTransform};
4use bevy::window::PrimaryWindow;
5
6const DEFAULT_FOCUSED_BORDER: Color = Color::srgb(0.38, 0.40, 0.43);
7const DEFAULT_UNFOCUSED_BORDER: Color = Color::BLACK;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub enum CuBevyMonSurface {
11    Sim,
12    #[default]
13    Monitor,
14}
15
16#[derive(Resource, Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub struct CuBevyMonFocus(pub CuBevyMonSurface);
18
19#[derive(Component, Clone, Copy, Debug, PartialEq, Eq)]
20pub struct CuBevyMonSurfaceNode(pub CuBevyMonSurface);
21
22#[derive(Component, Clone, Copy, Debug)]
23pub struct CuBevyMonFocusBorder {
24    pub surface: CuBevyMonSurface,
25    pub focused_color: Color,
26    pub unfocused_color: Color,
27    pub focused_width_px: f32,
28    pub unfocused_width_px: f32,
29}
30
31impl CuBevyMonFocusBorder {
32    pub fn new(surface: CuBevyMonSurface) -> Self {
33        Self {
34            surface,
35            focused_color: DEFAULT_FOCUSED_BORDER,
36            unfocused_color: DEFAULT_UNFOCUSED_BORDER,
37            focused_width_px: 1.0,
38            unfocused_width_px: 1.0,
39        }
40    }
41
42    pub fn with_colors(mut self, focused_color: Color, unfocused_color: Color) -> Self {
43        self.focused_color = focused_color;
44        self.unfocused_color = unfocused_color;
45        self
46    }
47
48    pub fn with_widths(mut self, focused_width_px: f32, unfocused_width_px: f32) -> Self {
49        self.focused_width_px = focused_width_px;
50        self.unfocused_width_px = unfocused_width_px;
51        self
52    }
53}
54
55pub(crate) fn update_surface_focus_from_click(
56    window: Single<&Window, With<PrimaryWindow>>,
57    mouse_buttons: Res<ButtonInput<MouseButton>>,
58    mut focus: ResMut<CuBevyMonFocus>,
59    surfaces: Query<(
60        &ComputedNode,
61        &UiGlobalTransform,
62        &ComputedStackIndex,
63        &CuBevyMonSurfaceNode,
64    )>,
65) {
66    if !mouse_buttons.just_pressed(MouseButton::Left) {
67        return;
68    }
69
70    let Some(cursor) = window.physical_cursor_position() else {
71        return;
72    };
73
74    let next_focus = surfaces
75        .iter()
76        .filter(|(node, transform, _, _)| node.contains_point(**transform, cursor))
77        .max_by_key(|(_, _, stack_index, _)| stack_index.0)
78        .map(|(_, _, _, surface)| surface.0);
79
80    if let Some(surface) = next_focus {
81        focus.0 = surface;
82    }
83}
84
85pub(crate) fn update_surface_focus_borders(
86    focus: Res<CuBevyMonFocus>,
87    mut nodes: Query<(&CuBevyMonFocusBorder, &mut BorderColor, &mut Node)>,
88) {
89    for (border, mut color, mut node) in &mut nodes {
90        let is_focused = focus.0 == border.surface;
91        let border_color = if is_focused {
92            border.focused_color
93        } else {
94            border.unfocused_color
95        };
96        let border_width = if is_focused {
97            border.focused_width_px
98        } else {
99            border.unfocused_width_px
100        };
101
102        *color = BorderColor::all(border_color);
103        node.border = UiRect::all(Val::Px(border_width));
104    }
105}
106
107pub(crate) fn local_cursor_position(
108    window: &Window,
109    node: &ComputedNode,
110    transform: &UiGlobalTransform,
111) -> Option<Vec2> {
112    let cursor = window.physical_cursor_position()?;
113    if !node.contains_point(*transform, cursor) {
114        return None;
115    }
116
117    transform
118        .try_inverse()
119        .map(|transform| transform.transform_point2(cursor) + 0.5 * node.size())
120}
121
122pub(crate) fn surface_local_rect(node_rect: Rect, transform: &UiGlobalTransform) -> Rect {
123    let (_, _, translation) = transform.to_scale_angle_translation();
124    Rect::from_corners(translation + node_rect.min, translation + node_rect.max)
125}