diff --git a/README.md b/README.md index 2f7fa49..2345688 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble ## todo -- show level info in editor - comments - accessibility - ui scaling diff --git a/src/editor.rs b/src/editor.rs index 7f116ff..7227ff3 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -21,8 +21,8 @@ use crate::{ const HEADER_HEIGHT: i32 = 40; const FOOTER_HEIGHT: i32 = 95; const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4; -const POPUP_WIDTH: i32 = 320; -const POPUP_HEIGHT: i32 = 165; +const END_POPUP_WIDTH: i32 = 320; +const END_POPUP_HEIGHT: i32 = 165; const MAX_ZOOM: f32 = 8.; const MIN_ZOOM: f32 = 0.25; @@ -56,9 +56,11 @@ pub struct Editor { exit_state: ExitState, exit_menu: bool, total_steps: usize, - popup: EndPopup, + popup: Popup, score: Option, tooltip: Tooltip, + mouse: MouseInput, + info_text: ShapedText, blueprints: Vec, selected_blueprint: usize, @@ -80,10 +82,12 @@ enum Action { } #[derive(Debug, PartialEq)] -enum EndPopup { +enum Popup { None, Success, Failure, + LevelInfo, + PauseMenu, Dismissed, } @@ -134,6 +138,8 @@ impl Editor { input_as_text = i.input().is_text(); machine.set_input(i.input().as_bytes().to_owned()); } + let mut info_text = ShapedText::new(20); + info_text.set_text(level.description()); Self { source_board: Board::parse(&solution.board), @@ -158,7 +164,8 @@ impl Editor { level, exit_state: ExitState::Dont, exit_menu: false, - popup: EndPopup::None, + popup: Popup::None, + info_text, score: solution.score, total_steps: 0, blueprints: get_blueprints(), @@ -172,6 +179,7 @@ impl Editor { undo_history: Vec::new(), undo_index: 0, tooltip: Tooltip::default(), + mouse: MouseInput::default(), } } @@ -292,18 +300,18 @@ impl Editor { self.machine.step(); if let Some(i) = self.stage { - if self.popup != EndPopup::None { - self.popup = EndPopup::Dismissed; + if self.popup != Popup::None { + self.popup = Popup::Dismissed; } let stage = &self.level.stages()[i]; - if self.popup == EndPopup::None { + if self.popup == Popup::None { if stage.output().as_bytes() == self.machine.output() { if i + 1 < self.level.stages().len() { self.stage = Some(i + 1); self.total_steps += self.machine.step_count(); self.reset_machine(); } else { - self.popup = EndPopup::Success; + self.popup = Popup::Success; println!("completed in {:?}", self.start_time.elapsed()); self.exit_state = ExitState::Save; self.sim_state = SimState::Stepping; @@ -313,7 +321,7 @@ impl Editor { }); } } else if !stage.output().as_bytes().starts_with(self.machine.output()) { - self.popup = EndPopup::Failure; + self.popup = Popup::Failure; self.sim_state = SimState::Stepping; } } @@ -435,9 +443,15 @@ impl Editor { } pub fn update(&mut self, rl: &RaylibHandle) { + self.tooltip.init_frame(rl); + self.mouse = MouseInput::get(rl); + if !matches!(self.popup, Popup::None | Popup::Dismissed) { + self.mouse.clear(); + } + if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) { self.sim_state = SimState::Editing; - self.popup = EndPopup::None; + self.popup = Popup::None; } if self.sim_state == SimState::Running { self.time_since_step += rl.get_frame_time(); @@ -478,27 +492,26 @@ impl Editor { } SimState::Running => { self.sim_state = SimState::Editing; - self.popup = EndPopup::None; + self.popup = Popup::None; } SimState::Stepping => self.sim_state = SimState::Running, } } - let mouse_pos = rl.get_mouse_position(); + let mouse_pos = self.mouse.pos(); if (self.active_tool != Tool::Blueprint || self.sim_state != SimState::Editing || mouse_pos.x > SIDEBAR_WIDTH as f32) && mouse_pos.y > HEADER_HEIGHT as f32 && (mouse_pos.y as i32) < (rl.get_screen_height() - FOOTER_HEIGHT) { - if rl.get_mouse_wheel_move() > 0. { - self.zoom_in(rl); - } - if rl.get_mouse_wheel_move() < 0. { - self.zoom_out(rl); + match self.mouse.scroll() { + Some(Scroll::Up) => self.zoom_in(rl), + Some(Scroll::Down) => self.zoom_out(rl), + None => (), } } - if rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_RIGHT) { + if self.mouse.right_hold() { let speed = if rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT) { 4. } else { @@ -562,105 +575,151 @@ impl Editor { } } - self.tooltip.init_frame(d); - self.draw_board(d, textures); self.board_overlay(d, textures); self.draw_bottom_bar(d, textures); self.draw_top_bar(d, textures); if self.active_tool == Tool::Blueprint { - let sidebar_height = d.get_screen_height() - FOOTER_HEIGHT - HEADER_HEIGHT - 40; + self.draw_blueprint_sidebar(d, textures); + } + + self.mouse = MouseInput::get(d); + + if !matches!(self.popup, Popup::None | Popup::Dismissed) { + self.tooltip.reset(); d.draw_rectangle( 0, - HEADER_HEIGHT + 20, - SIDEBAR_WIDTH, - sidebar_height, - Color::new(32, 32, 32, 255), + 0, + d.get_screen_width(), + d.get_screen_height(), + Color::new(100, 100, 100, 120), ); - d.draw_text("Blueprints", 10, HEADER_HEIGHT + 30, 20, Color::WHITE); - let mut y = HEADER_HEIGHT + 60; - let blueprints_shown = (sidebar_height as usize - 45) / 37; - let end = self - .blueprints - .len() - .min(blueprints_shown + self.blueprint_scroll); - for (i, b) in self.blueprints[self.blueprint_scroll..end] - .iter_mut() - .enumerate() - { - let i = i + self.blueprint_scroll; - self.tooltip.add(5, y, 32, 32, "Delete"); - if simple_button(d, 5, y, 32, 32) { - b.remove_file(); - self.blueprints.remove(i); - break; - } - draw_scaled_texture(d, textures.get("rubbish"), 5, y, 2.); - let is_selected = self.selected_blueprint == i; - let mut text_selected = is_selected && self.blueprint_name_selected; - text_input( - d, - Rectangle::new(42., y as f32, 200., 32.), - &mut b.name, - &mut text_selected, - 32, - is_selected, - ); - if is_selected { - self.blueprint_name_selected = text_selected; - } - self.tooltip.add(42 + 205, y, 32, 32, "Select"); - simple_option_button(d, 42 + 205, y, 32, 32, i, &mut self.selected_blueprint); + } - d.draw_texture_ex( - textures.get("blueprint"), - Vector2::new((42 + 205) as f32, y as f32), - 0., - 2., - Color::new(255, 255, 255, if is_selected { 255 } else { 150 }), - ); - y += 37; + match self.popup { + Popup::Success | Popup::Failure => { + self.draw_end_popup(d, textures); } + Popup::LevelInfo => { + let bounds = screen_centered_rect_dyn(d, 100, 100); + d.draw_rectangle_rec(bounds, BG_MEDIUM); + d.draw_text(self.level.name(), 110, 110, 30, Color::LIGHTBLUE); + self.info_text.update_width(d, bounds.width as i32 - 20); + self.info_text.draw(d, 110, 140); + } + _ => (), } self.tooltip.draw(d); + } - if matches!(self.popup, EndPopup::Success | EndPopup::Failure) { - let x = d.get_screen_width() / 2 - POPUP_WIDTH / 2; - let y = d.get_screen_height() / 2 - POPUP_HEIGHT / 2; - d.draw_rectangle(x, y, POPUP_WIDTH, POPUP_HEIGHT, BG_DARK); - if self.popup == EndPopup::Success { - d.draw_text("Level Complete!", x + 45, y + 10, 30, Color::LIME); - if let Some(score) = &self.score { - d.draw_text("cycles", x + 15, y + 45, 20, Color::WHITE); - draw_usize(d, textures, score.cycles, x + 10, y + 70, 9, 2); - d.draw_text("tiles", x + 215, y + 45, 20, Color::WHITE); - draw_usize(d, textures, score.tiles, x + 210, y + 70, 5, 2); - } - if simple_button(d, x + 10, y + 110, 140, 45) { - self.popup = EndPopup::Dismissed; - } - d.draw_text("continue\nediting", x + 15, y + 115, 20, Color::WHITE); - - if simple_button(d, x + POPUP_WIDTH / 2 + 5, y + 110, 140, 45) { - self.exit_state = ExitState::ExitAndSave; - } - d.draw_text( - "return to\nlevel list", - x + POPUP_WIDTH / 2 + 10, - y + 115, - 20, - Color::WHITE, - ); - } else { - d.draw_text("Level Failed!", x + 45, y + 10, 30, Color::RED); - d.draw_text("incorrect output", x + 45, y + 45, 20, Color::WHITE); - if simple_button(d, x + 10, y + 110, 300, 25) { - self.popup = EndPopup::Dismissed; - } - d.draw_text("ok :(", x + 15, y + 115, 20, Color::WHITE); + fn draw_blueprint_sidebar(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { + let sidebar_height = d.get_screen_height() - FOOTER_HEIGHT - HEADER_HEIGHT - 40; + d.draw_rectangle( + 0, + HEADER_HEIGHT + 20, + SIDEBAR_WIDTH, + sidebar_height, + Color::new(32, 32, 32, 255), + ); + d.draw_text("Blueprints", 10, HEADER_HEIGHT + 30, 20, Color::WHITE); + let mut y = HEADER_HEIGHT + 60; + let blueprints_shown = (sidebar_height as usize - 45) / 37; + let end = self + .blueprints + .len() + .min(blueprints_shown + self.blueprint_scroll); + for (i, b) in self.blueprints[self.blueprint_scroll..end] + .iter_mut() + .enumerate() + { + let i = i + self.blueprint_scroll; + self.tooltip.add(5, y, 32, 32, "Delete"); + if simple_button(d, &self.mouse, 5, y, 32, 32) { + b.remove_file(); + self.blueprints.remove(i); + break; } + draw_scaled_texture(d, textures.get("rubbish"), 5, y, 2.); + let is_selected = self.selected_blueprint == i; + let mut text_selected = is_selected && self.blueprint_name_selected; + text_input( + d, + &self.mouse, + Rectangle::new(42., y as f32, 200., 32.), + &mut b.name, + &mut text_selected, + 32, + is_selected, + ); + if is_selected { + self.blueprint_name_selected = text_selected; + } + self.tooltip.add(42 + 205, y, 32, 32, "Select"); + simple_option_button( + d, + &self.mouse, + 42 + 205, + y, + 32, + 32, + i, + &mut self.selected_blueprint, + ); + + d.draw_texture_ex( + textures.get("blueprint"), + Vector2::new((42 + 205) as f32, y as f32), + 0., + 2., + Color::new(255, 255, 255, if is_selected { 255 } else { 150 }), + ); + y += 37; + } + } + + fn draw_end_popup(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { + let x = d.get_screen_width() / 2 - END_POPUP_WIDTH / 2; + let y = d.get_screen_height() / 2 - END_POPUP_HEIGHT / 2; + d.draw_rectangle(x, y, END_POPUP_WIDTH, END_POPUP_HEIGHT, BG_DARK); + if self.popup == Popup::Success { + d.draw_text("Level Complete!", x + 45, y + 10, 30, Color::LIME); + if let Some(score) = &self.score { + d.draw_text("cycles", x + 15, y + 45, 20, Color::WHITE); + draw_usize(d, textures, score.cycles, x + 10, y + 70, 9, 2); + d.draw_text("tiles", x + 215, y + 45, 20, Color::WHITE); + draw_usize(d, textures, score.tiles, x + 210, y + 70, 5, 2); + } + if simple_button(d, &self.mouse, x + 10, y + 110, 140, 45) { + self.popup = Popup::Dismissed; + } + d.draw_text("continue\nediting", x + 15, y + 115, 20, Color::WHITE); + + if simple_button( + d, + &self.mouse, + x + END_POPUP_WIDTH / 2 + 5, + y + 110, + 140, + 45, + ) { + self.exit_state = ExitState::ExitAndSave; + } + d.draw_text( + "return to\nlevel list", + x + END_POPUP_WIDTH / 2 + 10, + y + 115, + 20, + Color::WHITE, + ); + } else { + d.draw_text("Level Failed!", x + 45, y + 10, 30, Color::RED); + d.draw_text("incorrect output", x + 45, y + 45, 20, Color::WHITE); + if simple_button(d, &self.mouse, x + 10, y + 110, 300, 25) { + self.popup = Popup::Dismissed; + } + d.draw_text("ok :(", x + 15, y + 115, 20, Color::WHITE); } } @@ -675,32 +734,37 @@ impl Editor { ); if self.exit_menu { - if simple_button(d, 4, 4, 32, 32) { + if simple_button(d, &self.mouse, 4, 4, 32, 32) { self.exit_state = ExitState::ExitAndSave; } self.tooltip.add(4, 4, 32, 32, "exit"); draw_scaled_texture(d, textures.get("exit"), 4, 4, 2.); - if simple_button(d, 38, 4, 32, 32) { + if simple_button(d, &self.mouse, 40, 4, 32, 32) { self.exit_menu = false; } - draw_scaled_texture(d, textures.get("cancel"), 38, 4, 2.); - self.tooltip.add(38, 4, 32, 32, "cancel"); + draw_scaled_texture(d, textures.get("cancel"), 40, 4, 2.); + self.tooltip.add(40, 4, 32, 32, "cancel"); } else { - if simple_button(d, 4, 4, 32, 32) { + if simple_button(d, &self.mouse, 4, 4, 32, 32) { self.exit_menu = true; } self.tooltip.add(4, 4, 32, 32, "exit"); draw_scaled_texture(d, textures.get("exit"), 4, 4, 2.); - if simple_button(d, 38, 4, 32, 32) { + if simple_button(d, &self.mouse, 40, 4, 32, 32) { self.exit_state = ExitState::Save; } - draw_scaled_texture(d, textures.get("save"), 38, 4, 2.); - self.tooltip.add(38, 4, 32, 32, "save"); + draw_scaled_texture(d, textures.get("save"), 40, 4, 2.); + self.tooltip.add(40, 4, 32, 32, "save"); } + if simple_button(d, &self.mouse, 76, 5, 60, 30) { + self.popup = Popup::LevelInfo; + } + d.draw_text("info", 80, 10, 20, Color::WHITE); + if self.sim_state == SimState::Editing { self.tooltip.add(150, 4, 32, 32, "Undo"); - if simple_button(d, 150, 4, 32, 32) { + if simple_button(d, &self.mouse, 150, 4, 32, 32) { self.undo() } let undo_icon = if self.undo_index > 0 { @@ -711,7 +775,7 @@ impl Editor { draw_scaled_texture(d, textures.get(undo_icon), 150, 4, 2.); self.tooltip.add(186, 4, 32, 32, "Redo"); - if simple_button(d, 186, 4, 32, 32) { + if simple_button(d, &self.mouse, 186, 4, 32, 32) { self.redo() } let redo_icon = if self.undo_index < self.undo_history.len() { @@ -723,7 +787,7 @@ impl Editor { } self.tooltip.add(223, 4, 32, 32, "Toggle overlay"); - if simple_button(d, 223, 4, 32, 32) { + if simple_button(d, &self.mouse, 223, 4, 32, 32) { self.draw_overlay = !self.draw_overlay; } let overlay_btn_icon = if self.draw_overlay { @@ -735,13 +799,13 @@ impl Editor { if self.sim_state == SimState::Running { self.tooltip.add(260, 4, 32, 32, "Pause"); - if simple_button(d, 260, 4, 32, 32) { + if simple_button(d, &self.mouse, 260, 4, 32, 32) { self.sim_state = SimState::Stepping; } draw_scaled_texture(d, textures.get("pause"), 260, 4, 2.); } else { self.tooltip.add(260, 4, 32, 32, "Start"); - if simple_button(d, 260, 4, 32, 32) { + if simple_button(d, &self.mouse, 260, 4, 32, 32) { if self.sim_state == SimState::Editing { self.init_sim() } @@ -752,22 +816,32 @@ impl Editor { if self.sim_state != SimState::Editing { self.tooltip.add(296, 4, 32, 32, "Stop"); - if simple_button(d, 296, 4, 32, 32) { + if simple_button(d, &self.mouse, 296, 4, 32, 32) { self.sim_state = SimState::Editing; - self.popup = EndPopup::None; + self.popup = Popup::None; } draw_scaled_texture(d, textures.get("stop"), 296, 4, 2.); } self.tooltip.add(332, 4, 32, 32, "Step"); - if simple_button(d, 332, 4, 32, 32) { + if simple_button(d, &self.mouse, 332, 4, 32, 32) { self.step_pressed(); } draw_scaled_texture(d, textures.get("step"), 332, 4, 2.); self.tooltip.add(368, 4, 48, 32, "Speed"); draw_usize(d, textures, 1 << self.sim_speed, 368, 4, SPEED_DIGITS, 1); - slider(d, &mut self.sim_speed, 0, MAX_SPEED_POWER, 368, 24, 48, 12); + slider( + d, + &self.mouse, + &mut self.sim_speed, + 0, + MAX_SPEED_POWER, + 368, + 24, + 48, + 12, + ); self.tooltip.add(420, 4, 180, 32, "Steps"); draw_usize(d, textures, self.machine.step_count(), 420, 4, 9, 2); @@ -781,7 +855,7 @@ impl Editor { draw_usize(d, textures, self.max_step_time as usize, 260, 60, 9, 1); d.draw_text("input:", 603, 8, 10, Color::WHITE); - if simple_button(d, 600, 20, 35, 15) { + if simple_button(d, &self.mouse, 600, 20, 35, 15) { self.input_as_text = !self.input_as_text } let input_mode_text = if self.input_as_text { "text" } else { "bytes" }; @@ -800,6 +874,7 @@ impl Editor { } if text_input( d, + &self.mouse, Rectangle::new(input_x as f32, 5., (width - input_x - 5) as f32, 30.), &mut input_text, &mut self.input_text_selected, @@ -853,6 +928,7 @@ impl Editor { hide_tile_tools = true; text_input( d, + &self.mouse, Rectangle::new(100., footer_top + 10., 240., 30.), &mut self.new_blueprint_name, &mut self.blueprint_name_selected, @@ -862,19 +938,21 @@ impl Editor { let y = footer_top as i32 + 49; self.tooltip.add(100, y, 40, 40, "Cancel"); - if simple_button(d, 100, y, 40, 40) || d.is_key_pressed(KeyboardKey::KEY_ESCAPE) { + if simple_button(d, &self.mouse, 100, y, 40, 40) + || d.is_key_pressed(KeyboardKey::KEY_ESCAPE) + { self.active_tool = Tool::SelectArea(Selection::default()); } draw_scaled_texture(d, textures.get("cancel"), 104, y + 4, 2.); self.tooltip.add(144, y, 40, 40, "Save blueprint"); - if simple_button(d, 144, y, 40, 40) { + if simple_button(d, &self.mouse, 144, y, 40, 40) { self.save_blueprint(selection); } draw_scaled_texture(d, textures.get("save"), 148, y + 4, 2.); self.tooltip.add(188, y, 40, 40, "Copy"); - if simple_button(d, 188, y, 40, 40) + if simple_button(d, &self.mouse, 188, y, 40, 40) || (d.is_key_pressed(KeyboardKey::KEY_C) && d.is_key_down(KeyboardKey::KEY_LEFT_CONTROL)) { @@ -884,7 +962,7 @@ impl Editor { draw_scaled_texture(d, textures.get("copy"), 192, y + 4, 2.); self.tooltip.add(232, y, 40, 40, "Delete"); - if simple_button(d, 232, y, 40, 40) { + if simple_button(d, &self.mouse, 232, y, 40, 40) { let min = selection.0.min(selection.1); let max = selection.0.max(selection.1); let board = @@ -894,7 +972,6 @@ impl Editor { draw_scaled_texture(d, textures.get("eraser"), 236, y + 4, 2.); } - let mouse_pos = d.get_mouse_position(); let mut tool_button = |(row, col): (i32, i32), texture: &str, tooltip: &'static str, tool_option: Tool| { let border = 4.; @@ -906,22 +983,20 @@ impl Editor { x: 100. + col as f32 * grid_size - if col < 0 { 10. } else { 0. }, y: footer_top + 5. + row as f32 * grid_size, }; - texture_option_button( + self.tooltip.add_rec( + Rectangle::new(pos.x, pos.y, button_size, button_size), + tooltip, + ); + scrollable_texture_option_button( d, + &self.mouse, pos, textures.get(texture), tool_option, &mut self.active_tool, tex_size, border, - ); - let bounds = Rectangle::new(pos.x, pos.y, button_size, button_size); - self.tooltip.add_rec(bounds, tooltip); - if bounds.check_collision_point_rec(mouse_pos) { - get_scroll(d) - } else { - None - } + ) }; tool_button((0, -2), "eraser", "Eraser", Tool::Erase); tool_button( @@ -1042,7 +1117,7 @@ impl Editor { let output_cell_width = 43; let output_cells = (d.get_screen_width() - output_x) as usize / output_cell_width as usize; - if simple_button(d, output_x, y + 70, 65, 15) { + if simple_button(d, &self.mouse, output_x, y + 70, 65, 15) { self.output_as_text = !self.output_as_text } let output_mode_text = if self.output_as_text { @@ -1101,8 +1176,7 @@ impl Editor { fn board_overlay(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32; - let mouse_pos = d.get_mouse_position(); - let scroll_delta = d.get_mouse_wheel_move(); + let tile_size = TILE_TEXTURE_SIZE * self.zoom; if self.sim_state == SimState::Editing { if let Some(board) = &self.pasting_board { @@ -1110,18 +1184,18 @@ impl Editor { self.pasting_board = None; return; } - if mouse_pos.y < footer_top && mouse_pos.y > HEADER_HEIGHT as f32 { + if self.mouse.pos().y < footer_top && self.mouse.pos().y > HEADER_HEIGHT as f32 { let view_offset = Vector2::new( self.view_offset.x.rem(tile_size), self.view_offset.y.rem(tile_size), ); - let mut offset = mouse_pos - view_offset; + let mut offset = self.mouse.pos() - view_offset; offset.x -= offset.x.rem(tile_size); offset.y -= offset.y.rem(tile_size); offset += view_offset; board.draw(d, textures, offset, self.zoom); - if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { - let tile_pos = (mouse_pos - self.view_offset) / tile_size; + if self.mouse.left_click() { + let tile_pos = (self.mouse.pos() - self.view_offset) / tile_size; let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor()); let board = self.pasting_board.take().unwrap(); self.set_area(tile_pos.into(), board); @@ -1157,8 +1231,8 @@ impl Editor { } } } - if mouse_pos.y < footer_top && mouse_pos.y > HEADER_HEIGHT as f32 { - let tile_pos = (mouse_pos - self.view_offset) / tile_size; + if self.mouse.pos().y < footer_top && self.mouse.pos().y > HEADER_HEIGHT as f32 { + let tile_pos = (self.mouse.pos() - self.view_offset) / tile_size; let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor()); let tile_screen_pos = self.pos_to_screen(tile_pos); @@ -1192,7 +1266,7 @@ impl Editor { Color::new(255, 255, 255, 100), ); } - if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { + if self.mouse.left_click() { let pos = tile_pos.into(); match self.active_tool { Tool::None | Tool::Erase | Tool::SelectArea(_) => (), @@ -1215,7 +1289,7 @@ impl Editor { } } Tool::Blueprint => { - if mouse_pos.x > SIDEBAR_WIDTH as f32 { + if self.mouse.pos().x > SIDEBAR_WIDTH as f32 { if let Some(bp) = self.blueprints.get(self.selected_blueprint) { let board = bp.get_board().unwrap().clone(); self.set_area(pos, board); @@ -1224,13 +1298,11 @@ impl Editor { } } } - if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) - && self.active_tool == Tool::Erase - { + if self.mouse.left_hold() && self.active_tool == Tool::Erase { self.set_tile(tile_pos.into(), Tile::BLANK); } if let Tool::SelectArea(selection) = &mut self.active_tool { - if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) { + if self.mouse.left_hold() { if selection.is_selecting { if let Some((_start, end)) = &mut selection.area { *end = tile_pos.into(); @@ -1241,7 +1313,7 @@ impl Editor { selection.area = Some((tile_pos.into(), tile_pos.into())); selection.is_selecting = true; } - } else if d.is_mouse_button_released(MouseButton::MOUSE_BUTTON_LEFT) { + } else if self.mouse.left_release() { selection.is_selecting = false; } } @@ -1251,18 +1323,20 @@ impl Editor { self.view_offset.x.rem(tile_size), self.view_offset.y.rem(tile_size), ); - let mut offset = mouse_pos - view_offset; + let mut offset = self.mouse.pos() - view_offset; offset.x -= offset.x.rem(tile_size); offset.y -= offset.y.rem(tile_size); offset += view_offset; bp.convert_board().draw(d, textures, offset, self.zoom); } - if mouse_pos.x < SIDEBAR_WIDTH as f32 { - if scroll_delta < 0. + if self.mouse.pos().x < SIDEBAR_WIDTH as f32 { + if self.mouse.scroll() == Some(Scroll::Down) && self.blueprint_scroll < self.blueprints.len().saturating_sub(5) { self.blueprint_scroll += 1; - } else if scroll_delta > 0. && self.blueprint_scroll > 0 { + } else if self.mouse.scroll() == Some(Scroll::Up) + && self.blueprint_scroll > 0 + { self.blueprint_scroll -= 1; } } diff --git a/src/main.rs b/src/main.rs index 760c56d..0cf3a8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,19 +123,16 @@ impl Game { let level_list_width = (d.get_screen_width() / 3).min(400); let screen_height = d.get_screen_height(); d.draw_rectangle(0, 0, level_list_width, screen_height, BG_MEDIUM); - - let clicked = d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT); - let mouse_pos = d.get_mouse_position(); - let scroll_delta = d.get_mouse_wheel_move(); + let mouse = MouseInput::get(d); const ENTRY_SPACING: i32 = 65; let fit_on_screen = (d.get_screen_height() / ENTRY_SPACING) as usize; let max_scroll = self.levels.len().saturating_sub(fit_on_screen); - if mouse_pos.x < level_list_width as f32 { - if scroll_delta < 0. && self.level_scroll < max_scroll { + if mouse.pos().x < level_list_width as f32 { + if mouse.scroll() == Some(Scroll::Down) && self.level_scroll < max_scroll { self.level_scroll += 1; } - if scroll_delta > 0. && self.level_scroll > 0 { + if mouse.scroll() == Some(Scroll::Up) && self.level_scroll > 0 { self.level_scroll -= 1; } } @@ -149,7 +146,7 @@ impl Game { width: level_list_width as f32 - 10., height: ENTRY_SPACING as f32 - 5., }; - let clicked_this = clicked && bounds.check_collision_point_rec(mouse_pos); + let clicked_this = mouse.left_click() && mouse.is_over(bounds); match level { LevelListEntry::Chapter(title, level_count) => { d.draw_rectangle_rec(bounds, BG_DARK); @@ -210,6 +207,7 @@ impl Game { for (solution_index, solution) in solutions.iter().enumerate() { if simple_option_button( d, + &mouse, level_list_width + 10, solution_y, entry_width, @@ -242,7 +240,14 @@ impl Game { } let next_id = get_free_id(solutions, Solution::id); - if simple_button(d, level_list_width + 10, solution_y, entry_width, 30) { + if simple_button( + d, + &mouse, + level_list_width + 10, + solution_y, + entry_width, + 30, + ) { self.selected_solution = solutions.len(); solutions.push(Solution::new(level, next_id)); } @@ -259,6 +264,7 @@ impl Game { let bounds = Rectangle::new(column_x as f32, y as f32, 220., 30.); if text_input( d, + &mouse, bounds, &mut solution.name, &mut self.editing_solution_name, @@ -270,7 +276,7 @@ impl Game { let id_text = format!("{}", solution.id()); d.draw_text(&id_text, column_x, y + 35, 10, Color::GRAY); - if simple_button(d, column_x, y + 50, 220, 30) { + if simple_button(d, &mouse, column_x, y + 50, 220, 30) { let cloned = solution.new_copy(next_id); self.selected_solution = solutions.len(); solutions.push(cloned); @@ -278,7 +284,7 @@ impl Game { } d.draw_text("clone", column_x + 5, y + 55, 20, Color::WHITE); - if simple_button(d, column_x, y + 85, 220, 30) { + if simple_button(d, &mouse, column_x, y + 85, 220, 30) { let mut editor = Editor::new(solution.clone(), level.clone()); editor.center_view(d); self.open_editor = Some(editor); diff --git a/src/ui.rs b/src/ui.rs index c4de24b..7ef9660 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use crate::{draw_scaled_texture, theme::*, Textures}; +use crate::{draw_scaled_texture, theme::*, MouseInput, Scroll, Textures}; use raylib::prelude::*; #[derive(Debug)] @@ -99,7 +99,7 @@ pub struct Tooltip { } impl Tooltip { - pub fn init_frame(&mut self, d: &mut RaylibDrawHandle) { + pub fn init_frame(&mut self, d: &RaylibHandle) { let p = d.get_mouse_position(); *self = Self { text: None, @@ -120,6 +120,10 @@ impl Tooltip { } } + pub fn reset(&mut self) { + self.text = None; + } + pub fn add_rec(&mut self, bounds: Rectangle, text: &'static str) { self.add( bounds.x as i32, @@ -153,8 +157,15 @@ impl Tooltip { } } -pub fn simple_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, height: i32) -> bool { - let mouse_pos = d.get_mouse_position(); +pub fn simple_button( + d: &mut RaylibDrawHandle, + mouse: &MouseInput, + x: i32, + y: i32, + width: i32, + height: i32, +) -> bool { + let mouse_pos = mouse.pos(); let bounds = Rectangle { x: x as f32, y: y as f32, @@ -162,13 +173,14 @@ pub fn simple_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, heigh height: height as f32, }; let hover = bounds.check_collision_point_rec(mouse_pos); - let pressed = hover && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT); + let pressed = hover && mouse.left_click(); d.draw_rectangle(x, y, width, height, widget_bg(hover)); pressed } pub fn simple_option_button( d: &mut RaylibDrawHandle, + mouse: &MouseInput, x: i32, y: i32, width: i32, @@ -181,12 +193,8 @@ where { let bounds = Rectangle::new(x as f32, y as f32, width as f32, height as f32); d.draw_rectangle_rec(bounds, widget_bg(&option == current)); - let mouse_pos = d.get_mouse_position(); let mut changed = false; - if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) - && bounds.check_collision_point_rec(mouse_pos) - && current != &option - { + if mouse.left_click() && mouse.is_over(bounds) && current != &option { *current = option; changed = true; } @@ -195,6 +203,7 @@ where pub fn text_input( d: &mut RaylibDrawHandle, + mouse: &MouseInput, bounds: Rectangle, text: &mut String, is_selected: &mut bool, @@ -224,11 +233,7 @@ pub fn text_input( 20, Color::WHITE, ); - let mouse_pos = d.get_mouse_position(); - if editable - && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) - && (bounds.check_collision_point_rec(mouse_pos) || *is_selected) - { + if editable && mouse.left_click() && (mouse.is_over(bounds) || *is_selected) { *is_selected = !*is_selected; } @@ -256,16 +261,17 @@ pub fn text_input( changed } -pub fn texture_option_button( +pub fn scrollable_texture_option_button( d: &mut RaylibDrawHandle, + mouse: &MouseInput, pos: Vector2, texture: &Texture2D, option: T, current: &mut T, tex_size: f32, border: f32, - // tooltip -) where +) -> Option +where T: PartialEq, { let bounds = Rectangle { @@ -289,13 +295,13 @@ pub fn texture_option_button( tex_size / texture.width as f32, Color::WHITE, ); - - let mouse_pos = d.get_mouse_position(); - if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) - && bounds.check_collision_point_rec(mouse_pos) - { - *current = option; + if mouse.is_over(bounds) { + if mouse.left_click() { + *current = option; + } + return mouse.scroll(); } + None } pub fn draw_usize( @@ -350,6 +356,7 @@ pub fn draw_usize_small( pub fn slider( d: &mut RaylibDrawHandle, + mouse: &MouseInput, value: &mut u8, min: u8, max: u8, @@ -362,38 +369,19 @@ pub fn slider( let percent = (*value - min + 1) as f32 / (max - min + 1) as f32; d.draw_rectangle(x, y, width, height, BG_WIDGET); d.draw_rectangle(x, y, (width as f32 * percent) as i32, height, Color::CYAN); - let mouse_pos = d.get_mouse_position(); let bounds = Rectangle::new(x as f32, y as f32, width as f32, height as f32); - if bounds.check_collision_point_rec(mouse_pos) { - if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) { - let percent = (mouse_pos.x - bounds.x) / bounds.width; + if mouse.is_over(bounds) { + if mouse.left_hold() { + let percent = (mouse.pos().x - bounds.x) / bounds.width; let new_value = min + (percent * (max - min + 1) as f32) as u8; if *value != new_value { *value = new_value; } - } else if d.get_mouse_wheel_move() > 0.5 && *value < max { + } else if mouse.scroll() == Some(Scroll::Up) && *value < max { *value += 1; - } else if d.get_mouse_wheel_move() < -0.5 && *value > min { + } else if mouse.scroll() == Some(Scroll::Down) && *value > min { *value -= 1; } } false } - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Scroll { - Up, - Down, -} - -pub fn get_scroll(rl: &RaylibHandle) -> Option { - const SCROLL_THRESHOLD: f32 = 0.5; - let value = rl.get_mouse_wheel_move(); - if value > SCROLL_THRESHOLD { - Some(Scroll::Up) - } else if value < -SCROLL_THRESHOLD { - Some(Scroll::Down) - } else { - None - } -} diff --git a/src/util.rs b/src/util.rs index a06960f..de8cd9d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -50,3 +50,87 @@ pub fn get_free_id(items: &[T], id_fn: fn(&T) -> usize) -> usize { } id } + +pub fn screen_centered_rect_dyn(rl: &RaylibHandle, margin_x: i32, margin_y: i32) -> Rectangle { + let w = rl.get_screen_width(); + let h = rl.get_screen_height(); + Rectangle { + x: margin_x as f32, + y: margin_y as f32, + width: (w - margin_x * 2) as f32, + height: (h - margin_y * 2) as f32, + } +} + +#[derive(Debug, Default)] +pub struct MouseInput { + pos: Vector2, + left_click: bool, + left_hold: bool, + left_release: bool, + right_hold: bool, + scroll: Option, +} + +impl MouseInput { + pub fn get(rl: &RaylibHandle) -> Self { + Self { + pos: rl.get_mouse_position(), + left_click: rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT), + left_hold: rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT), + left_release: rl.is_mouse_button_released(MouseButton::MOUSE_BUTTON_LEFT), + right_hold: rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_RIGHT), + scroll: get_scroll(rl), + } + } + + pub fn is_over(&self, rect: Rectangle) -> bool { + rect.check_collision_point_rec(self.pos) + } + + pub fn clear(&mut self) { + *self = Self::default(); + } + + pub fn pos(&self) -> Vector2 { + self.pos + } + + pub fn left_click(&self) -> bool { + self.left_click + } + + pub fn left_hold(&self) -> bool { + self.left_hold + } + + pub fn left_release(&self) -> bool { + self.left_release + } + + pub fn right_hold(&self) -> bool { + self.right_hold + } + + pub fn scroll(&self) -> Option { + self.scroll + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Scroll { + Up, + Down, +} + +pub fn get_scroll(rl: &RaylibHandle) -> Option { + const SCROLL_THRESHOLD: f32 = 0.5; + let value = rl.get_mouse_wheel_move(); + if value > SCROLL_THRESHOLD { + Some(Scroll::Up) + } else if value < -SCROLL_THRESHOLD { + Some(Scroll::Down) + } else { + None + } +}