diff --git a/.gitignore b/.gitignore index a768d82..90254f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target /user *.zip -version.txt diff --git a/Makefile b/Makefile index a864adf..39d733e 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,11 @@ -VERSION = `cat version.txt` -RELEASE_DIRNAME = marble_machinations_${VERSION} -BIN_NAME = marble-machinations - -TARGET_W64 = x86_64-pc-windows-gnu +release_name = marble_machinations +bin_name = marble2 linux: cargo build --release - mkdir ${RELEASE_DIRNAME} - cp target/release/${BIN_NAME} ${RELEASE_DIRNAME}/ - cp -r assets levels ${RELEASE_DIRNAME}/ - zip -r ${RELEASE_DIRNAME}_linux.zip ${RELEASE_DIRNAME}/ - rm -rf ${RELEASE_DIRNAME} - -windows: - cargo build --release --target=${TARGET_W64} - mkdir ${RELEASE_DIRNAME}_win - cp target/${TARGET_W64}/release/${BIN_NAME}.exe ${RELEASE_DIRNAME}_win/ - cp -r assets levels ${RELEASE_DIRNAME}_win/ - zip -r ${RELEASE_DIRNAME}_win.zip ${RELEASE_DIRNAME}_win/ - rm -rf ${RELEASE_DIRNAME}_win + mkdir ${release_name} + cp target/release/${bin_name} ${release_name}/ + cp -r assets levels ${release_name}/ + zip -r ${release_name}_linux.zip ${release_name}/ + rm -rf ${release_name} diff --git a/assets/marble_overlay.png b/assets/marble_overlay.png deleted file mode 100644 index ecdb128..0000000 Binary files a/assets/marble_overlay.png and /dev/null differ diff --git a/build.rs b/build.rs deleted file mode 100644 index dd69605..0000000 --- a/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::{fs::File, io::Write}; - -fn main(){ - let version = concat!("v", env!("CARGO_PKG_VERSION")); - File::create("version.txt").unwrap().write_all(version.as_bytes()).unwrap(); -} \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 165b2fa..2f5833d 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -9,13 +9,19 @@ use raylib::prelude::*; use crate::{ blueprint::Blueprint, + draw_scaled_texture, draw_usize, get_free_id, get_scroll, level::Level, - marble_engine::{board::*, pos::*, tile::*, Machine}, - solution::*, + marble_engine::{ + board::{Board, ResizeDeltas}, + pos::{Pos, PosInt}, + tile::{Claim, Comparison, Direction, MathOp, MirrorType, OpenTile, PTile, Tile, WireType}, + Machine, + }, + simple_button, simple_option_button, simple_toggle_button, slider, + solution::{Score, Solution}, + text_input, texture_option_button, theme::*, - ui::*, - util::*, - TILE_TEXTURE_SIZE, + userdata_dir, Scroll, Textures, Tooltip, TILE_TEXTURE_SIZE, }; const HEADER_HEIGHT: i32 = 40; @@ -718,17 +724,8 @@ 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"); - if simple_button(d, 223, 4, 32, 32) { - self.draw_overlay = !self.draw_overlay; - } - let overlay_btn_icon = if self.draw_overlay { - "marble_overlay" - } else { - "marble" - }; - draw_scaled_texture(d, textures.get(overlay_btn_icon), 223, 4, 2.); if self.sim_state == SimState::Running { self.tooltip.add(260, 4, 32, 32, "Pause"); @@ -858,19 +855,16 @@ 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) { 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) { 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) || (d.is_key_pressed(KeyboardKey::KEY_C) && d.is_key_down(KeyboardKey::KEY_LEFT_CONTROL)) @@ -880,7 +874,6 @@ 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) { let min = selection.0.min(selection.1); let max = selection.0.max(selection.1); diff --git a/src/main.rs b/src/main.rs index a96f7da..8697c43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,13 +12,11 @@ mod marble_engine; mod solution; mod theme; mod util; -mod ui; use editor::{Editor, ExitState}; use level::Level; use solution::Solution; use theme::*; -use ui::{simple_button, simple_option_button, text_input, ShapedText}; use util::*; const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSION")); diff --git a/src/marble_engine.rs b/src/marble_engine.rs index a373f30..32adb3e 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -7,7 +7,7 @@ use board::Board; use pos::*; use tile::*; -use crate::{ ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE}; +use crate::{draw_usize_small, Textures, TILE_TEXTURE_SIZE}; #[derive(Debug)] pub struct Machine { diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index c4de24b..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,399 +0,0 @@ -use std::ops::Range; - -use crate::{draw_scaled_texture, theme::*, Textures}; -use raylib::prelude::*; - -#[derive(Debug)] -pub struct ShapedText { - text: String, - max_width: i32, - font_size: i32, - lines: Vec>, -} - -impl ShapedText { - pub fn new(font_size: i32) -> Self { - Self { - text: String::new(), - font_size, - max_width: 500, - lines: Vec::new(), - } - } - - pub fn set_text(&mut self, text: &str) { - if text != self.text { - self.text = text.to_owned(); - self.lines.clear(); - } - } - - pub fn height(&self) -> i32 { - self.font_size * self.lines.len() as i32 - } - - pub fn update_width(&mut self, d: &RaylibHandle, width: i32) { - if self.max_width == width && !self.lines.is_empty() { - return; - } - self.max_width = width; - self.lines.clear(); - // todo remove leading space on broken lines - // todo fix splitting very long words - let mut line_start = 0; - let mut line_end = 0; - for (i, c) in self.text.char_indices() { - if c == ' ' { - let line = &self.text[line_start..i]; - let new_line_length = d.measure_text(line, self.font_size); - if new_line_length <= self.max_width { - line_end = i; - } else { - self.lines.push(line_start..line_end); - line_start = line_end; - } - } - if c == '\n' { - let line = &self.text[line_start..i]; - let new_line_length = d.measure_text(line, self.font_size); - if new_line_length <= self.max_width { - self.lines.push(line_start..i); - line_end = i + 1; - line_start = i + 1; - } else { - self.lines.push(line_start..line_end); - self.lines.push(line_end..(i + 1)); - line_start = i + 1; - line_end = i + 1; - } - } - } - let i = self.text.len(); - let line = &self.text[line_start..i]; - let new_line_length = d.measure_text(line, self.font_size); - if new_line_length <= self.max_width { - self.lines.push(line_start..i); - } else { - self.lines.push(line_start..line_end); - self.lines.push(line_end..i); - } - } - - pub fn draw(&self, d: &mut RaylibDrawHandle, x: i32, y: i32) { - // d.draw_rectangle(x, y, self.max_width, 4, Color::RED); - for (i, line) in self.lines.iter().enumerate() { - let line = &self.text[line.clone()]; - let line_y = y + self.font_size * i as i32; - d.draw_text(line, x, line_y, self.font_size, Color::WHITE); - } - } -} - -#[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_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, height: i32) -> bool { - let mouse_pos = d.get_mouse_position(); - let bounds = Rectangle { - x: x as f32, - y: y as f32, - width: width as f32, - 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); - d.draw_rectangle(x, y, width, height, widget_bg(hover)); - pressed -} - -pub fn simple_option_button( - d: &mut RaylibDrawHandle, - x: i32, - y: i32, - width: i32, - height: i32, - option: T, - current: &mut T, -) -> bool -where - T: PartialEq, -{ - 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 - { - *current = option; - changed = true; - } - changed -} - -pub fn text_input( - d: &mut RaylibDrawHandle, - bounds: Rectangle, - text: &mut String, - is_selected: &mut bool, - max_len: usize, - editable: bool, -) -> bool { - let mut changed = false; - d.draw_rectangle_rec(bounds, widget_bg(*is_selected)); - d.draw_rectangle_rec( - Rectangle::new( - bounds.x + 2., - bounds.y + bounds.height - 5., - bounds.width - 4., - 3., - ), - BG_DARK, - ); - let drawn_text = if *is_selected { - &format!("{text}_") - } else { - text.as_str() - }; - d.draw_text( - drawn_text, - bounds.x as i32 + 4, - bounds.y as i32 + 4, - 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) - { - *is_selected = !*is_selected; - } - - if *is_selected { - if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) { - *is_selected = false; - } - if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() { - changed = true; - text.pop(); - } - if text.len() < max_len { - let char_code = unsafe { ffi::GetCharPressed() }; - let c = if char_code > 0 { - char::from_u32(char_code as u32) - } else { - None - }; - if let Some(c) = c { - changed = true; - text.push(c); - } - } - } - changed -} - -pub fn texture_option_button( - d: &mut RaylibDrawHandle, - pos: Vector2, - texture: &Texture2D, - option: T, - current: &mut T, - tex_size: f32, - border: f32, - // tooltip -) where - T: PartialEq, -{ - let bounds = Rectangle { - x: pos.x, - y: pos.y, - width: tex_size + border * 2., - height: tex_size + border * 2., - }; - d.draw_rectangle_rec( - bounds, - if &option == current { - BG_WIDGET_ACTIVE - } else { - gray(16) - }, - ); - d.draw_texture_ex( - texture, - pos + Vector2::new(border, border), - 0., - 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; - } -} - -pub fn draw_usize( - d: &mut RaylibDrawHandle, - textures: &Textures, - number: usize, - x: i32, - y: i32, - digits: u8, - scale: u8, -) { - let digits = digits as i32; - let scale = scale as i32; - for i in 0..digits { - d.draw_rectangle(x + 10 * i * scale, y, 8 * scale, 16 * scale, BG_LIGHT); - } - let mut num = number; - let mut i = 0; - while (num != 0 || i == 0) && i < digits { - let texture = textures.get(&format!("digit_{}", num % 10)); - let x = x + (digits - i - 1) * 10 * scale; - draw_scaled_texture(d, texture, x, y, scale as f32); - num /= 10; - i += 1; - } -} - -pub fn draw_usize_small( - d: &mut RaylibDrawHandle, - textures: &Textures, - mut num: usize, - mut x: i32, - y: i32, - scale: f32, -) { - const MAX_DIGITS: usize = 8; - let mut digits = [0; MAX_DIGITS]; - let mut i = 0; - while (num != 0 || i == 0) && i < MAX_DIGITS { - digits[MAX_DIGITS - i - 1] = num % 10; - num /= 10; - i += 1; - } - let texture = textures.get("digits_small"); - for &digit in &digits[(MAX_DIGITS - i)..] { - let source = Rectangle::new(4. * digit as f32, 0., 4., 6.); - let dest = Rectangle::new(x as f32, y as f32, 4. * scale, 6. * scale); - d.draw_texture_pro(texture, source, dest, Vector2::zero(), 0., FG_MARBLE_VALUE); - x += 4 * scale as i32; - } -} - -pub fn slider( - d: &mut RaylibDrawHandle, - value: &mut u8, - min: u8, - max: u8, - x: i32, - y: i32, - width: i32, - height: i32, -) -> bool { - // the +1 makes the lowest state look slightly filled and the max state fully filled - 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; - 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 { - *value += 1; - } else if d.get_mouse_wheel_move() < -0.5 && *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..0e727b2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,9 @@ -use std::{collections::HashMap, fs::read_dir, path::PathBuf}; +use std::{collections::HashMap, fs::read_dir, ops::Range, path::PathBuf}; use raylib::prelude::*; +use crate::theme::*; + #[derive(Default)] pub struct Textures { map: HashMap, @@ -28,6 +30,415 @@ impl Textures { } } +pub fn simple_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, height: i32) -> bool { + let mouse_pos = d.get_mouse_position(); + let bounds = Rectangle { + x: x as f32, + y: y as f32, + width: width as f32, + 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); + d.draw_rectangle(x, y, width, height, widget_bg(hover)); + pressed +} + +#[derive(Debug)] +pub struct ShapedText { + text: String, + max_width: i32, + font_size: i32, + lines: Vec>, +} + +impl ShapedText { + pub fn new(font_size: i32) -> Self { + Self { + text: String::new(), + font_size, + max_width: 500, + lines: Vec::new(), + } + } + + pub fn set_text(&mut self, text: &str) { + if text != self.text { + self.text = text.to_owned(); + self.lines.clear(); + } + } + + pub fn height(&self) -> i32 { + self.font_size * self.lines.len() as i32 + } + + pub fn update_width(&mut self, d: &RaylibHandle, width: i32) { + if self.max_width == width && !self.lines.is_empty() { + return; + } + self.max_width = width; + self.lines.clear(); + // todo remove leading space on broken lines + // todo fix splitting very long words + let mut line_start = 0; + let mut line_end = 0; + for (i, c) in self.text.char_indices() { + if c == ' ' { + let line = &self.text[line_start..i]; + let new_line_length = d.measure_text(line, self.font_size); + if new_line_length <= self.max_width { + line_end = i; + } else { + self.lines.push(line_start..line_end); + line_start = line_end; + } + } + if c == '\n' { + let line = &self.text[line_start..i]; + let new_line_length = d.measure_text(line, self.font_size); + if new_line_length <= self.max_width { + self.lines.push(line_start..i); + line_end = i + 1; + line_start = i + 1; + } else { + self.lines.push(line_start..line_end); + self.lines.push(line_end..(i + 1)); + line_start = i + 1; + line_end = i + 1; + } + } + } + let i = self.text.len(); + let line = &self.text[line_start..i]; + let new_line_length = d.measure_text(line, self.font_size); + if new_line_length <= self.max_width { + self.lines.push(line_start..i); + } else { + self.lines.push(line_start..line_end); + self.lines.push(line_end..i); + } + } + + pub fn draw(&self, d: &mut RaylibDrawHandle, x: i32, y: i32) { + // d.draw_rectangle(x, y, self.max_width, 4, Color::RED); + for (i, line) in self.lines.iter().enumerate() { + let line = &self.text[line.clone()]; + let line_y = y + self.font_size * i as i32; + d.draw_text(line, x, line_y, self.font_size, Color::WHITE); + } + } +} + +#[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, + x: i32, + y: i32, + width: i32, + height: i32, + margin: i32, +) { + let mouse_pos = d.get_mouse_position(); + let bounds = Rectangle { + x: x as f32, + y: y as f32, + width: width as f32, + height: height as f32, + }; + let hover = bounds.check_collision_point_rec(mouse_pos); + d.draw_rectangle(x, y, width, height, widget_bg(hover)); + if *state { + d.draw_rectangle( + x + margin, + y + margin, + width - margin * 2, + height - margin * 2, + FG_TOGGLE_ENABLED, + ); + } + if hover && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { + *state = !*state; + } +} + +pub fn simple_option_button( + d: &mut RaylibDrawHandle, + x: i32, + y: i32, + width: i32, + height: i32, + option: T, + current: &mut T, +) -> bool +where + T: PartialEq, +{ + 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 + { + *current = option; + changed = true; + } + changed +} + +pub fn text_input( + d: &mut RaylibDrawHandle, + bounds: Rectangle, + text: &mut String, + is_selected: &mut bool, + max_len: usize, + editable: bool, +) -> bool { + let mut changed = false; + d.draw_rectangle_rec(bounds, widget_bg(*is_selected)); + d.draw_rectangle_rec( + Rectangle::new( + bounds.x + 2., + bounds.y + bounds.height - 5., + bounds.width - 4., + 3., + ), + BG_DARK, + ); + let drawn_text = if *is_selected { + &format!("{text}_") + } else { + text.as_str() + }; + d.draw_text( + drawn_text, + bounds.x as i32 + 4, + bounds.y as i32 + 4, + 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) + { + *is_selected = !*is_selected; + } + + if *is_selected { + if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) { + *is_selected = false; + } + if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() { + changed = true; + text.pop(); + } + if text.len() < max_len { + let char_code = unsafe { ffi::GetCharPressed() }; + let c = if char_code > 0 { + char::from_u32(char_code as u32) + } else { + None + }; + if let Some(c) = c { + changed = true; + text.push(c); + } + } + } + changed +} + +pub fn texture_option_button( + d: &mut RaylibDrawHandle, + pos: Vector2, + texture: &Texture2D, + option: T, + current: &mut T, + tex_size: f32, + border: f32, + // tooltip +) where + T: PartialEq, +{ + let bounds = Rectangle { + x: pos.x, + y: pos.y, + width: tex_size + border * 2., + height: tex_size + border * 2., + }; + d.draw_rectangle_rec( + bounds, + if &option == current { + BG_WIDGET_ACTIVE + } else { + gray(16) + }, + ); + d.draw_texture_ex( + texture, + pos + Vector2::new(border, border), + 0., + 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; + } +} + +pub fn draw_usize( + d: &mut RaylibDrawHandle, + textures: &Textures, + number: usize, + x: i32, + y: i32, + digits: u8, + scale: u8, +) { + let digits = digits as i32; + let scale = scale as i32; + for i in 0..digits { + d.draw_rectangle(x + 10 * i * scale, y, 8 * scale, 16 * scale, BG_LIGHT); + } + let mut num = number; + let mut i = 0; + while (num != 0 || i == 0) && i < digits { + let texture = textures.get(&format!("digit_{}", num % 10)); + let x = x + (digits - i - 1) * 10 * scale; + draw_scaled_texture(d, texture, x, y, scale as f32); + num /= 10; + i += 1; + } +} + +pub fn draw_usize_small( + d: &mut RaylibDrawHandle, + textures: &Textures, + mut num: usize, + mut x: i32, + y: i32, + scale: f32, +) { + const MAX_DIGITS: usize = 8; + let mut digits = [0; MAX_DIGITS]; + let mut i = 0; + while (num != 0 || i == 0) && i < MAX_DIGITS { + digits[MAX_DIGITS - i - 1] = num % 10; + num /= 10; + i += 1; + } + let texture = textures.get("digits_small"); + for &digit in &digits[(MAX_DIGITS - i)..] { + let source = Rectangle::new(4. * digit as f32, 0., 4., 6.); + let dest = Rectangle::new(x as f32, y as f32, 4. * scale, 6. * scale); + d.draw_texture_pro(texture, source, dest, Vector2::zero(), 0., FG_MARBLE_VALUE); + x += 4 * scale as i32; + } +} + +pub fn slider( + d: &mut RaylibDrawHandle, + value: &mut u8, + min: u8, + max: u8, + x: i32, + y: i32, + width: i32, + height: i32, +) -> bool { + // the +1 makes the lowest state look slightly filled and the max state fully filled + 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; + 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 { + *value += 1; + } else if d.get_mouse_wheel_move() < -0.5 && *value > min { + *value -= 1; + } + } + false +} + pub fn userdata_dir() -> PathBuf { PathBuf::from("user") } @@ -43,6 +454,24 @@ pub fn draw_scaled_texture( d.draw_texture_ex(texture, pos, 0., scale, Color::WHITE); } +#[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 + } +} + pub fn get_free_id(items: &[T], id_fn: fn(&T) -> usize) -> usize { let mut id = 0; while items.iter().any(|i| id_fn(i) == id) {