1use sdl2::event::Event;
10use sdl2::pixels::Color as SdlColor;
11use sdl2::rect::Rect;
12use sdl2::render::{Canvas, Texture, TextureCreator};
13use sdl2::surface::Surface;
14use sdl2::video::Window;
15use sdl2::image::LoadSurface;
16
17#[derive(Debug, Clone)]
22pub enum MouseEvent {
23 Moved { x: i32, y: i32, dx: i32, dy: i32 },
24 LeftClick { x: i32, y: i32 },
25 LeftDoubleClick { x: i32, y: i32 },
26 RightClick { x: i32, y: i32 },
27 RightDoubleClick { x: i32, y: i32 },
28 MiddleClick { x: i32, y: i32 },
29 ButtonDown { x: i32, y: i32, button: sdl2::mouse::MouseButton },
30 ButtonUp { x: i32, y: i32, button: sdl2::mouse::MouseButton },
31 Wheel { x: i32, y: i32, direction: MouseWheelDirection },
32 EnterWindow,
33 LeaveWindow,
34}
35
36#[derive(Debug, Clone)]
37pub enum MouseWheelDirection { Normal, Flipped }
38
39pub struct MouseState {
40 pub x: i32, pub y: i32,
41 pub left_down: bool, pub right_down: bool, pub middle_down: bool,
42 pub scroll_x: i32, pub scroll_y: i32,
43 last_click_time: std::time::Instant,
44 last_click_pos: (i32, i32),
45}
46
47impl MouseState {
48 pub fn new() -> Self {
49 Self { x: 0, y: 0, left_down: false, right_down: false, middle_down: false,
50 scroll_x: 0, scroll_y: 0,
51 last_click_time: std::time::Instant::now(), last_click_pos: (0, 0) }
52 }
53
54 pub fn process_event(&mut self, event: &Event) -> Option<MouseEvent> {
55 match event {
56 Event::MouseMotion { x, y, xrel, yrel, .. } => {
57 self.x = *x; self.y = *y;
58 Some(MouseEvent::Moved { x: *x, y: *y, dx: *xrel, dy: *yrel })
59 }
60 Event::MouseButtonDown { mouse_btn, x, y, clicks, .. } => {
61 self.x = *x; self.y = *y;
62 let now = std::time::Instant::now();
63 let is_double = now.duration_since(self.last_click_time) < std::time::Duration::from_millis(500)
64 && *x == self.last_click_pos.0 && *y == self.last_click_pos.1 && *clicks == 2;
65 self.last_click_time = now; self.last_click_pos = (*x, *y);
66 match mouse_btn {
67 sdl2::mouse::MouseButton::Left => {
68 self.left_down = true;
69 Some(if is_double { MouseEvent::LeftDoubleClick { x: *x, y: *y } }
70 else { MouseEvent::LeftClick { x: *x, y: *y } })
71 }
72 sdl2::mouse::MouseButton::Right => {
73 self.right_down = true;
74 Some(if is_double { MouseEvent::RightDoubleClick { x: *x, y: *y } }
75 else { MouseEvent::RightClick { x: *x, y: *y } })
76 }
77 sdl2::mouse::MouseButton::Middle => {
78 self.middle_down = true;
79 Some(MouseEvent::MiddleClick { x: *x, y: *y })
80 }
81 _ => Some(MouseEvent::ButtonDown { x: *x, y: *y, button: *mouse_btn }),
82 }
83 }
84 Event::MouseButtonUp { mouse_btn, x, y, .. } => {
85 self.x = *x; self.y = *y;
86 match mouse_btn {
87 sdl2::mouse::MouseButton::Left => self.left_down = false,
88 sdl2::mouse::MouseButton::Right => self.right_down = false,
89 sdl2::mouse::MouseButton::Middle => self.middle_down = false,
90 _ => {}
91 }
92 Some(MouseEvent::ButtonUp { x: *x, y: *y, button: *mouse_btn })
93 }
94 Event::MouseWheel { x, y, direction, .. } => {
95 self.scroll_x = *x; self.scroll_y = *y;
96 let dir = match direction {
97 sdl2::mouse::MouseWheelDirection::Normal => MouseWheelDirection::Normal,
98 sdl2::mouse::MouseWheelDirection::Flipped => MouseWheelDirection::Flipped,
99 _ => MouseWheelDirection::Normal,
100 };
101 Some(MouseEvent::Wheel { x: *x, y: *y, direction: dir })
102 }
103 Event::Window { win_event: sdl2::event::WindowEvent::Leave, .. } => Some(MouseEvent::LeaveWindow),
104 Event::Window { win_event: sdl2::event::WindowEvent::Enter, .. } => Some(MouseEvent::EnterWindow),
105 _ => None,
106 }
107 }
108}
109
110impl Default for MouseState { fn default() -> Self { Self::new() } }
111
112pub struct TextTexture {
117 pub texture: Texture<'static>,
118 pub width: u32,
119 pub height: u32,
120}
121
122pub struct TtfFont {
123 texture_creator: std::rc::Rc<TextureCreator<sdl2::video::WindowContext>>,
124 font_path: String,
125 font_size: u16,
126}
127
128impl TtfFont {
129 pub fn new(path: String, size: u16, tc: std::rc::Rc<TextureCreator<sdl2::video::WindowContext>>) -> Self {
130 Self { texture_creator: tc, font_path: path, font_size: size }
131 }
132
133 pub fn render_text(&self, text: &str, color: SdlColor) -> Option<TextTexture> {
134 let ttf = sdl2::ttf::init().ok()?;
135 let font = ttf.load_font(&self.font_path, self.font_size).ok()?;
136 let surface = font.render(text).blended(color).ok()?;
137 let (w, h) = surface.size();
138 let texture = self.texture_creator.create_texture_from_surface(&surface).ok()?;
139 let st: Texture<'static> = unsafe { std::mem::transmute(texture) };
140 Some(TextTexture { texture: st, width: w, height: h })
141 }
142
143 pub fn measure_text(&self, text: &str) -> (u32, u32) {
144 if let Ok(ttf) = sdl2::ttf::init() {
145 if let Ok(font) = ttf.load_font(&self.font_path, self.font_size) {
146 return font.size_of(text).unwrap_or((0, 0));
147 }
148 }
149 (text.len() as u32 * 8, self.font_size as u32)
150 }
151}
152
153pub struct Sdl2Core {
158 pub canvas: Canvas<Window>,
159 pub event_pump: sdl2::EventPump,
160 pub mouse: MouseState,
161 pub texture_creator: std::rc::Rc<TextureCreator<sdl2::video::WindowContext>>,
162 pub font: Option<TtfFont>,
163 pub width: i32, pub height: i32,
164}
165
166impl Sdl2Core {
167 pub fn new(title: &str, width: i32, height: i32) -> Result<Self, String> {
168 sdl2::hint::set("SDL_VIDEODRIVER", "x11");
169 sdl2::hint::set("SDL_RENDER_DRIVER", "opengles2");
170 sdl2::hint::set("SDL_VIDEO_X11_FORCE_EGL", "1");
171 sdl2::hint::set("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH", "1");
172 sdl2::hint::set("SDL_HINT_TOUCH_MOUSE_EVENTS", "1");
173 sdl2::hint::set("SDL_HINT_ENABLE_SCREEN_KEYBOARD", "1");
174 sdl2::hint::set("SDL_HINT_IME_SHOW_UI", "1");
175
176 let sdl = sdl2::init().map_err(|e| e.to_string())?;
177 let video = sdl.video().map_err(|e| e.to_string())?;
178 sdl2::ttf::init().map_err(|e| e.to_string())?;
179 unsafe { sdl2::sys::SDL_StartTextInput(); }
180
181 let window = video.window(title, width as u32, height as u32)
182 .position_centered().opengl().build().map_err(|e| e.to_string())?;
183 let canvas = window.into_canvas().accelerated().present_vsync()
184 .build().map_err(|e| e.to_string())?;
185 let texture_creator = std::rc::Rc::new(canvas.texture_creator());
186 let event_pump = sdl.event_pump().map_err(|e| e.to_string())?;
187
188 let font = Self::load_system_font(std::rc::Rc::clone(&texture_creator));
189
190 Ok(Self { canvas, event_pump, mouse: MouseState::new(), texture_creator, font, width, height })
191 }
192
193 fn load_system_font(tc: std::rc::Rc<TextureCreator<sdl2::video::WindowContext>>) -> Option<TtfFont> {
194 for path in &["/system/fonts/Roboto-Regular.ttf", "/system/fonts/DroidSans.ttf",
195 "/data/data/com.termux/files/usr/share/fonts/DejaVuSans.ttf"] {
196 if std::path::Path::new(path).exists() {
197 println!("[SDL2-TTF] Fuente: {}", path);
198 return Some(TtfFont::new((*path).into(), 16, tc));
199 }
200 }
201 eprintln!("[SDL2-TTF] Sin fuente del sistema");
202 None
203 }
204
205 pub fn poll_events(&mut self) -> Vec<MouseEvent> {
206 let mut evts = Vec::new();
207 for event in self.event_pump.poll_iter() {
208 if let Some(me) = self.mouse.process_event(&event) { evts.push(me); }
209 }
210 evts
211 }
212
213 pub fn draw_text(&mut self, text: &str, x: i32, y: i32, color: SdlColor) {
214 if let Some(ref font) = self.font {
215 if let Some(tex) = font.render_text(text, color) {
216 let _ = self.canvas.copy(&tex.texture, None, Rect::new(x, y, tex.width, tex.height));
217 }
218 }
219 }
220
221 pub fn load_sprite(&self, path: &str) -> Option<Sprite> {
222 Sprite::load(&self.canvas, path).ok()
223 }
224}
225
226pub struct Sprite {
231 texture: Texture<'static>,
232 width: u32, height: u32,
233}
234
235impl Sprite {
236 pub fn load(canvas: &Canvas<Window>, path: &str) -> Result<Self, String> {
237 let tc = canvas.texture_creator();
238 let surface: Surface<'_> = Surface::from_file(path).map_err(|e| e.to_string())?;
239 let (w, h) = surface.size();
240 let texture = tc.create_texture_from_surface(&surface).map_err(|e| e.to_string())?;
241 let st: Texture<'static> = unsafe { std::mem::transmute(texture) };
242 Ok(Self { texture: st, width: w, height: h })
243 }
244
245 pub fn draw(&self, canvas: &mut Canvas<Window>, x: i32, y: i32, scale: u32) {
246 let _ = canvas.copy(&self.texture, None, Rect::new(x, y, self.width * scale, self.height * scale));
247 }
248
249 pub fn size(&self) -> (u32, u32) { (self.width, self.height) }
250}