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::{
|
use crate::{
|
||||||
blueprint::Blueprint,
|
blueprint::Blueprint,
|
||||||
draw_scaled_texture, draw_usize, get_free_id, get_scroll,
|
|
||||||
level::Level,
|
level::Level,
|
||||||
marble_engine::{
|
marble_engine::{board::*, pos::*, tile::*, Machine},
|
||||||
board::{Board, ResizeDeltas},
|
solution::*,
|
||||||
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::*,
|
theme::*,
|
||||||
userdata_dir, Scroll, Textures, Tooltip, TILE_TEXTURE_SIZE,
|
ui::*,
|
||||||
|
util::*,
|
||||||
|
TILE_TEXTURE_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const HEADER_HEIGHT: i32 = 40;
|
const HEADER_HEIGHT: i32 = 40;
|
||||||
|
|
|
@ -12,11 +12,13 @@ mod marble_engine;
|
||||||
mod solution;
|
mod solution;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
use editor::{Editor, ExitState};
|
use editor::{Editor, ExitState};
|
||||||
use level::Level;
|
use level::Level;
|
||||||
use solution::Solution;
|
use solution::Solution;
|
||||||
use theme::*;
|
use theme::*;
|
||||||
|
use ui::{simple_button, simple_option_button, text_input, ShapedText};
|
||||||
use util::*;
|
use util::*;
|
||||||
|
|
||||||
const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSION"));
|
const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
|
@ -7,7 +7,7 @@ use board::Board;
|
||||||
use pos::*;
|
use pos::*;
|
||||||
use tile::*;
|
use tile::*;
|
||||||
|
|
||||||
use crate::{draw_usize_small, Textures, TILE_TEXTURE_SIZE};
|
use crate::{ ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Machine {
|
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 raylib::prelude::*;
|
||||||
|
|
||||||
use crate::theme::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Textures {
|
pub struct Textures {
|
||||||
map: HashMap<String, Texture2D>,
|
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 {
|
pub fn userdata_dir() -> PathBuf {
|
||||||
PathBuf::from("user")
|
PathBuf::from("user")
|
||||||
}
|
}
|
||||||
|
@ -454,24 +43,6 @@ pub fn draw_scaled_texture(
|
||||||
d.draw_texture_ex(texture, pos, 0., scale, Color::WHITE);
|
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 {
|
pub fn get_free_id<T>(items: &[T], id_fn: fn(&T) -> usize) -> usize {
|
||||||
let mut id = 0;
|
let mut id = 0;
|
||||||
while items.iter().any(|i| id_fn(i) == id) {
|
while items.iter().any(|i| id_fn(i) == id) {
|
||||||
|
|
Loading…
Reference in a new issue