diff --git a/README.md b/README.md index c93bcfe..f5244a7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble - make direct power (comparator -> machine) work, (needs storing power direction in machine tiles) - cut selections, copy to system clipboard - timestamps in solutions and blueprints -- tooltips - lock tile types for early levels to make it less overwhelming - display tool variant more clearly (it's not obvious there are more states) - option to use 8-bit marbles? diff --git a/src/editor.rs b/src/editor.rs index bd6bcbe..2f5833d 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -21,7 +21,7 @@ use crate::{ solution::{Score, Solution}, text_input, texture_option_button, theme::*, - userdata_dir, Scroll, Textures, TILE_TEXTURE_SIZE, + userdata_dir, Scroll, Textures, Tooltip, TILE_TEXTURE_SIZE, }; const HEADER_HEIGHT: i32 = 40; @@ -64,6 +64,7 @@ pub struct Editor { total_steps: usize, popup: EndPopup, score: Option, + tooltip: Tooltip, blueprints: Vec, selected_blueprint: usize, @@ -176,6 +177,7 @@ impl Editor { draw_overlay: true, undo_history: Vec::new(), undo_index: 0, + tooltip: Tooltip::default(), } } @@ -567,6 +569,8 @@ impl Editor { } } + self.tooltip.init_frame(d); + self.draw_board(d, textures); self.board_overlay(d, textures); self.draw_bottom_bar(d, textures); @@ -593,6 +597,7 @@ impl Editor { .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); @@ -612,6 +617,7 @@ impl Editor { 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( @@ -625,6 +631,8 @@ impl Editor { } } + 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; @@ -694,6 +702,7 @@ impl Editor { } if self.sim_state == SimState::Editing { + self.tooltip.add(150, 4, 32, 32, "Undo"); if simple_button(d, 150, 4, 32, 32) { self.undo() } @@ -704,6 +713,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) { self.redo() } @@ -715,48 +725,48 @@ impl Editor { draw_scaled_texture(d, textures.get(redo_icon), 186, 4, 2.); } simple_toggle_button(d, &mut self.draw_overlay, 223, 4, 32, 32, 4); + self.tooltip.add(223, 4, 32, 32, "Toggle overlay"); - match self.sim_state { - SimState::Editing => { - if simple_button(d, 260, 4, 32, 32) { - self.init_sim(); - self.sim_state = SimState::Running; - } - draw_scaled_texture(d, textures.get("play"), 260, 4, 2.); + if self.sim_state == SimState::Running { + self.tooltip.add(260, 4, 32, 32, "Pause"); + if simple_button(d, 260, 4, 32, 32) { + self.sim_state = SimState::Stepping; } - SimState::Running => { - if simple_button(d, 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 self.sim_state == SimState::Editing { + self.init_sim() } - draw_scaled_texture(d, textures.get("pause"), 260, 4, 2.); - if simple_button(d, 296, 4, 32, 32) { - self.sim_state = SimState::Editing; - self.popup = EndPopup::None; - } - draw_scaled_texture(d, textures.get("stop"), 296, 4, 2.); - } - SimState::Stepping => { - if simple_button(d, 260, 4, 32, 32) { - self.sim_state = SimState::Running; - } - draw_scaled_texture(d, textures.get("play"), 260, 4, 2.); - if simple_button(d, 296, 4, 32, 32) { - self.sim_state = SimState::Editing; - self.popup = EndPopup::None; - } - draw_scaled_texture(d, textures.get("stop"), 296, 4, 2.); + self.sim_state = SimState::Running; } + draw_scaled_texture(d, textures.get("play"), 260, 4, 2.); } + + if self.sim_state != SimState::Editing { + self.tooltip.add(296, 4, 32, 32, "Stop"); + if simple_button(d, 296, 4, 32, 32) { + self.sim_state = SimState::Editing; + self.popup = EndPopup::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) { 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); + self.tooltip.add(420, 4, 180, 32, "Steps"); draw_usize(d, textures, self.machine.step_count(), 420, 4, 9, 2); if self.stage > Some(0) { + self.tooltip.add(420, 44, 180, 32, "Total steps"); let total_steps = self.total_steps + self.machine.step_count(); draw_usize(d, textures, total_steps, 420, 44, 9, 2); } @@ -875,50 +885,88 @@ impl Editor { } let mouse_pos = d.get_mouse_position(); - let mut tool_button = |(row, col): (i32, i32), texture: &str, tool_option: Tool| { - let border = 4.; - let gap = 2.; - let tex_size = 32.; - let button_size = tex_size + border * 2.; - let grid_size = button_size + gap * 2.; - let pos = Vector2 { - x: 100. + col as f32 * grid_size - if col < 0 { 10. } else { 0. }, - y: footer_top + 5. + row as f32 * grid_size, + let mut tool_button = + |(row, col): (i32, i32), texture: &str, tooltip: &'static str, tool_option: Tool| { + let border = 4.; + let gap = 2.; + let tex_size = 32.; + let button_size = tex_size + border * 2.; + let grid_size = button_size + gap * 2.; + let pos = Vector2 { + 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( + d, + 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 + } }; - texture_option_button( - d, - 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); - if bounds.check_collision_point_rec(mouse_pos) { - get_scroll(d) - } else { - None - } - }; - tool_button((0, -2), "eraser", Tool::Erase); - tool_button((1, -2), "selection", Tool::SelectArea(Selection::default())); + tool_button((0, -2), "eraser", "Eraser", Tool::Erase); + tool_button( + (1, -2), + "selection", + "Select", + Tool::SelectArea(Selection::default()), + ); - tool_button((0, -1), "blueprint", Tool::Blueprint); - tool_button((1, -1), "transparent", Tool::None); + tool_button((0, -1), "blueprint", "Blueprints", Tool::Blueprint); + tool_button((1, -1), "transparent", "None", Tool::None); if !hide_tile_tools { - tool_button((0, 0), "block", Tool::SetTile(Tile::from_char('#'))); - tool_button((0, 1), "silo_off", Tool::SetTile(Tile::from_char('B'))); - tool_button((0, 2), "button_off", Tool::SetTile(Tile::from_char('*'))); - tool_button((0, 3), "io_tile_off", Tool::SetTile(Tile::from_char('I'))); - tool_button((0, 4), "flipper_off", Tool::SetTile(Tile::from_char('F'))); - tool_button((0, 5), "digit_tool", Tool::Digits(None)); + tool_button( + (0, 0), + "block", + "Block", + Tool::SetTile(Tile::from_char('#')), + ); + tool_button( + (0, 1), + "silo_off", + "Silo", + Tool::SetTile(Tile::from_char('B')), + ); + tool_button( + (0, 2), + "button_off", + "Button", + Tool::SetTile(Tile::from_char('*')), + ); + tool_button( + (0, 3), + "io_tile_off", + "Input/Output silo", + Tool::SetTile(Tile::from_char('I')), + ); + tool_button( + (0, 4), + "flipper_off", + "Flipper", + Tool::SetTile(Tile::from_char('F')), + ); + tool_button((0, 5), "digit_tool", "Digit", Tool::Digits(None)); - tool_button((1, 0), "marble", Tool::SetTile(Tile::from_char('o'))); + tool_button( + (1, 0), + "marble", + "Marble", + Tool::SetTile(Tile::from_char('o')), + ); match tool_button( (1, 1), - Tile::Wire(self.tool_wire, false).texture(), + self.tool_wire.texture_name_off(), + self.tool_wire.human_name(), Tool::Wire, ) { Some(Scroll::Down) => self.tool_wire.next(), @@ -926,14 +974,20 @@ impl Editor { None => (), } - match tool_button((1, 2), Tile::Arrow(self.tool_arrow).texture(), Tool::Arrow) { + match tool_button( + (1, 2), + self.tool_arrow.arrow_tile_texture_name(), + self.tool_arrow.arrow_tile_human_name(), + Tool::Arrow, + ) { Some(Scroll::Down) => self.tool_arrow = self.tool_arrow.right(), Some(Scroll::Up) => self.tool_arrow = self.tool_arrow.left(), None => (), } if tool_button( (1, 3), - Tile::Mirror(self.tool_mirror).texture(), + self.tool_mirror.texture_name(), + self.tool_mirror.human_name(), Tool::Mirror, ) .is_some() @@ -942,7 +996,8 @@ impl Editor { } match tool_button( (1, 4), - Tile::Powerable(PTile::Math(self.tool_math), false).texture(), + self.tool_math.texture_name_off(), + self.tool_math.human_name(), Tool::Math, ) { Some(Scroll::Down) => self.tool_math.next(), @@ -951,7 +1006,8 @@ impl Editor { } match tool_button( (1, 5), - Tile::Powerable(PTile::Comparator(self.tool_comparator), false).texture(), + self.tool_comparator.texture_name_off(), + self.tool_comparator.human_name(), Tool::Comparator, ) { Some(Scroll::Down) => self.tool_comparator.next(), diff --git a/src/marble_engine/tile.rs b/src/marble_engine/tile.rs index 3619c19..8fc0809 100644 --- a/src/marble_engine/tile.rs +++ b/src/marble_engine/tile.rs @@ -270,6 +270,15 @@ impl Direction { } } + pub const fn arrow_tile_human_name(self) -> &'static str { + match self { + Direction::Up => "Up Arrow", + Direction::Down => "Down Arrow", + Direction::Left => "Left Arrow", + Direction::Right => "Right Arrow", + } + } + pub const fn arrow_texture_name(self) -> &'static str { match self { Direction::Up => "direction_up", @@ -320,6 +329,7 @@ impl WireType { WireType::Cross => "wire_cross_on", } } + pub const fn texture_name_off(self) -> &'static str { match self { WireType::Vertical => "wire_vertical_off", @@ -327,6 +337,14 @@ impl WireType { WireType::Cross => "wire_cross_off", } } + + pub const fn human_name(self) -> &'static str { + match self { + WireType::Vertical => "Vertical Wire", + WireType::Horizontal => "Horizontal Wire", + WireType::Cross => "Wire Cross", + } + } } impl MirrorType { @@ -360,9 +378,26 @@ impl MirrorType { MirrorType::Back => "mirror_back", } } + + pub const fn human_name(self) -> &'static str { + match self { + MirrorType::Forward => "Mirror A", + MirrorType::Back => "Mirror B", + } + } } impl MathOp { + pub const fn human_name(self) -> &'static str { + match self { + MathOp::Add => "Math: Add", + MathOp::Sub => "Math: Subtract", + MathOp::Mul => "Math: Multiply", + MathOp::Div => "Math: Divide", + MathOp::Rem => "Math: Remainder", + } + } + pub const fn texture_name_on(self) -> &'static str { match self { MathOp::Add => "add_on", @@ -372,6 +407,7 @@ impl MathOp { MathOp::Rem => "rem_on", } } + pub const fn texture_name_off(self) -> &'static str { match self { MathOp::Add => "add_off", @@ -404,6 +440,15 @@ impl MathOp { } impl Comparison { + pub const fn human_name(self) -> &'static str { + match self { + Comparison::LessThan => "Comparator: Less than", + Comparison::GreaterThan => "Comparator: Greater than", + Comparison::Equal => "Comparator: Equal", + Comparison::NotEqual => "Comparator: Not Equal", + } + } + pub const fn texture_name_on(self) -> &'static str { match self { Comparison::LessThan => "lt_on", @@ -412,6 +457,7 @@ impl Comparison { Comparison::NotEqual => "neq_on", } } + pub const fn texture_name_off(self) -> &'static str { match self { Comparison::LessThan => "lt_off", diff --git a/src/util.rs b/src/util.rs index 4a9ac2d..08f2ea7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -44,6 +44,70 @@ pub fn simple_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, heigh pressed } +#[derive(Debug, Default)] +pub struct Tooltip { + text: Option<&'static str>, + mouse_x: i32, + mouse_y: i32, + screen_width: i32, + screen_height: i32, +} + +impl Tooltip { + pub fn init_frame(&mut self, d: &mut RaylibDrawHandle) { + let p = d.get_mouse_position(); + *self = Self { + text: None, + mouse_x: p.x as i32, + mouse_y: p.y as i32, + screen_width: d.get_screen_width(), + screen_height: d.get_screen_height(), + }; + } + + pub fn add(&mut self, x: i32, y: i32, width: i32, height: i32, text: &'static str) { + if self.mouse_x >= x + && self.mouse_y >= y + && self.mouse_x <= (x + width) + && self.mouse_y <= (y + height) + { + self.text = Some(text); + } + } + + pub fn add_rec(&mut self, bounds: Rectangle, text: &'static str) { + self.add( + bounds.x as i32, + bounds.y as i32, + bounds.width as i32, + bounds.height as i32, + text, + ); + } + + pub fn draw(&self, d: &mut RaylibDrawHandle) { + if let Some(text) = self.text { + let font_size = 20; + let margin = 4; + let text_width = d.measure_text(text, font_size); + let x = self + .mouse_x + .min(self.screen_width - text_width - margin * 2); + let y = self + .mouse_y + .min(self.screen_height - font_size - margin * 2); + d.draw_rectangle( + x, + y, + text_width + margin * 2, + font_size + margin * 2, + BG_LIGHT, + ); + d.draw_text(text, x + margin, y + margin, font_size, Color::WHITE); + } + } +} + pub fn simple_toggle_button( d: &mut RaylibDrawHandle, state: &mut bool,