1use bevy::math::Rect;
2use bevy::prelude::*;
3use bevy::ui::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<(&ComputedNode, &UiGlobalTransform, &CuBevyMonSurfaceNode)>,
60) {
61 if !mouse_buttons.just_pressed(MouseButton::Left) {
62 return;
63 }
64
65 let Some(cursor) = window.physical_cursor_position() else {
66 return;
67 };
68
69 let next_focus = surfaces
70 .iter()
71 .filter(|(node, transform, _)| node.contains_point(**transform, cursor))
72 .max_by_key(|(node, _, _)| node.stack_index())
73 .map(|(_, _, surface)| surface.0);
74
75 if let Some(surface) = next_focus {
76 focus.0 = surface;
77 }
78}
79
80pub(crate) fn update_surface_focus_borders(
81 focus: Res<CuBevyMonFocus>,
82 mut nodes: Query<(&CuBevyMonFocusBorder, &mut BorderColor, &mut Node)>,
83) {
84 for (border, mut color, mut node) in &mut nodes {
85 let is_focused = focus.0 == border.surface;
86 let border_color = if is_focused {
87 border.focused_color
88 } else {
89 border.unfocused_color
90 };
91 let border_width = if is_focused {
92 border.focused_width_px
93 } else {
94 border.unfocused_width_px
95 };
96
97 *color = BorderColor::all(border_color);
98 node.border = UiRect::all(Val::Px(border_width));
99 }
100}
101
102pub(crate) fn local_cursor_position(
103 window: &Window,
104 node: &ComputedNode,
105 transform: &UiGlobalTransform,
106) -> Option<Vec2> {
107 let cursor = window.physical_cursor_position()?;
108 if !node.contains_point(*transform, cursor) {
109 return None;
110 }
111
112 transform
113 .try_inverse()
114 .map(|transform| transform.transform_point2(cursor) + 0.5 * node.size())
115}
116
117pub(crate) fn surface_local_rect(node_rect: Rect, transform: &UiGlobalTransform) -> Rect {
118 let (_, _, translation) = transform.to_scale_angle_translation();
119 Rect::from_corners(translation + node_rect.min, translation + node_rect.max)
120}