move ui helpers to own module
This commit is contained in:
parent
da12e8519e
commit
12b39467e3
5 changed files with 440 additions and 442 deletions
|
@ -9,19 +9,13 @@ use raylib::prelude::*;
|
|||
|
||||
use crate::{
|
||||
blueprint::Blueprint,
|
||||
draw_scaled_texture, draw_usize, get_free_id, get_scroll,
|
||||
level::Level,
|
||||
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,
|
||||
marble_engine::{board::*, pos::*, tile::*, Machine},
|
||||
solution::*,
|
||||
theme::*,
|
||||
userdata_dir, Scroll, Textures, Tooltip, TILE_TEXTURE_SIZE,
|
||||
ui::*,
|
||||
util::*,
|
||||
TILE_TEXTURE_SIZE,
|
||||
};
|
||||
|
||||
const HEADER_HEIGHT: i32 = 40;
|
||||
|
|
|
@ -12,11 +12,13 @@ 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"));
|
||||
|
|
|
@ -7,7 +7,7 @@ use board::Board;
|
|||
use pos::*;
|
||||
use tile::*;
|
||||
|
||||
use crate::{draw_usize_small, Textures, TILE_TEXTURE_SIZE};
|
||||
use crate::{ ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Machine {
|
||||
|
|
431
src/ui.rs
Normal file
431
src/ui.rs
Normal file
|
@ -0,0 +1,431 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::{draw_scaled_texture, theme::*, Textures};
|
||||
use raylib::prelude::*;
|
||||
|
||||
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<Range<usize>>,
|
||||
}
|
||||
|
||||
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<T>(
|
||||
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<T>(
|
||||
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<Scroll> {
|
||||
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
|
||||
}
|
||||
}
|
431
src/util.rs
431
src/util.rs
|
@ -1,9 +1,7 @@
|
|||
use std::{collections::HashMap, fs::read_dir, ops::Range, path::PathBuf};
|
||||
use std::{collections::HashMap, fs::read_dir, path::PathBuf};
|
||||
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::theme::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Textures {
|
||||
map: HashMap<String, Texture2D>,
|
||||
|
@ -30,415 +28,6 @@ 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<Range<usize>>,
|
||||
}
|
||||
|
||||
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<T>(
|
||||
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<T>(
|
||||
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")
|
||||
}
|
||||
|
@ -454,24 +43,6 @@ 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<Scroll> {
|
||||
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<T>(items: &[T], id_fn: fn(&T) -> usize) -> usize {
|
||||
let mut id = 0;
|
||||
while items.iter().any(|i| id_fn(i) == id) {
|
||||
|
|
Loading…
Reference in a new issue