base_ui/widget/widgets/
context_menu.rs1use crate::graphics::Renderer;
2use crate::style::color::Color;
3use std::sync::Arc;
4use std::cell::RefCell;
5use log::debug;
6
7pub struct MenuItem {
8 text: String,
9 on_click: Option<Arc<RefCell<dyn FnMut() + 'static>>>,
10 background_color: Color,
11 text_color: Color,
12 hover_background_color: Color,
13 hover_text_color: Color,
14 is_hovered: bool,
15}
16
17impl MenuItem {
18 pub fn new(text: &str) -> Self {
19 debug!("Creating new menu item: {}", text);
20 Self {
21 text: text.to_string(),
22 on_click: None,
23 background_color: Color::new(0.9, 0.9, 0.9, 1.0),
24 text_color: Color::new(0.1, 0.1, 0.1, 1.0),
25 hover_background_color: Color::new(0.7, 0.7, 0.9, 1.0),
26 hover_text_color: Color::new(1.0, 1.0, 1.0, 1.0),
27 is_hovered: false,
28 }
29 }
30
31 pub fn set_on_click<F>(&mut self, callback: F) where F: FnMut() + 'static {
32 debug!("Setting click handler for menu item: {}", self.text);
33 self.on_click = Some(Arc::new(RefCell::new(callback)));
34 }
35}
36
37pub struct ContextMenu {
38 x: f32,
39 y: f32,
40 width: f32,
41 items: Vec<MenuItem>,
42 visible: bool,
43 item_height: f32,
44 padding: f32,
45 border_color: Color,
46 border_width: f32,
47}
48
49impl ContextMenu {
50 pub fn new() -> Self {
51 Self {
52 x: 0.0,
53 y: 0.0,
54 width: 200.0,
55 items: Vec::new(),
56 visible: false,
57 item_height: 30.0,
58 padding: 5.0,
59 border_color: Color::new(0.8, 0.8, 0.8, 1.0),
60 border_width: 1.0,
61 }
62 }
63
64 pub fn add_item(&mut self, item: MenuItem) {
65 debug!("Adding menu item: {}", item.text);
66 self.items.push(item);
67 }
68
69 pub fn show(&mut self, x: f32, y: f32) {
70 debug!("Showing context menu at ({}, {})", x, y);
71 self.x = x;
72 self.y = y;
73 self.visible = true;
74 }
75
76 pub fn hide(&mut self) {
77 debug!("Hiding context menu");
78 self.visible = false;
79 }
80
81 pub fn draw(&self, renderer: &mut Renderer, screen_width: f32, screen_height: f32) {
82 if !self.visible {
83 return;
84 }
85
86 let total_height = (self.items.len() as f32) * self.item_height;
87
88 renderer.draw_rect(
90 self.x - self.border_width,
91 self.y - self.border_width,
92 self.width + self.border_width * 2.0,
93 total_height + self.border_width * 2.0,
94 self.border_color.to_array()
95 );
96
97 for (i, item) in self.items.iter().enumerate() {
99 let item_y = self.y + (i as f32) * self.item_height;
100 let bg_color = if item.is_hovered {
101 item.hover_background_color
102 } else {
103 item.background_color
104 };
105 let text_color = if item.is_hovered { item.hover_text_color } else { item.text_color };
106
107 renderer.draw_rect(self.x, item_y, self.width, self.item_height, bg_color.to_array());
109
110 renderer
112 .text_renderer_mut()
113 .render_text(
114 &item.text,
115 self.x + self.padding,
116 item_y + (self.item_height - 20.0) / 2.0,
117 20.0,
118 screen_width,
119 screen_height,
120 text_color.to_array()
121 );
122 }
123 }
124
125 pub fn handle_mouse_move(&mut self, x: f32, y: f32) {
126 if !self.visible {
127 return;
128 }
129
130 for (i, item) in self.items.iter_mut().enumerate() {
131 let item_y = self.y + (i as f32) * self.item_height;
132 item.is_hovered =
133 x >= self.x &&
134 x <= self.x + self.width &&
135 y >= item_y &&
136 y <= item_y + self.item_height;
137 }
138 }
139
140 pub fn handle_click(&mut self, x: f32, y: f32) -> bool {
141 if !self.visible {
142 return false;
143 }
144
145 let total_height = (self.items.len() as f32) * self.item_height;
146 if x < self.x || x > self.x + self.width || y < self.y || y > self.y + total_height {
147 debug!("Click outside menu bounds, hiding menu");
148 self.hide();
149 return false;
150 }
151
152 for (i, item) in self.items.iter_mut().enumerate() {
153 let item_y = self.y + (i as f32) * self.item_height;
154 if y >= item_y && y <= item_y + self.item_height {
155 debug!("Menu item clicked: {}", item.text);
156 if let Some(callback) = &item.on_click {
157 callback.borrow_mut()();
158 }
159 self.hide();
160 return true;
161 }
162 }
163 false
164 }
165}