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}