Support signed numbers in number picker
This commit is contained in:
parent
92ccc482ae
commit
b5e1711a56
|
@ -1,10 +1,15 @@
|
|||
use std::ops::{Bound, RangeBounds};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Bound, RangeBounds},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use egui::{
|
||||
ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
|
||||
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
||||
WidgetText,
|
||||
};
|
||||
use num_traits::PrimInt;
|
||||
|
||||
pub trait UiExt {
|
||||
fn section(&mut self, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui));
|
||||
|
@ -86,37 +91,47 @@ impl UiExt for Ui {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct NumberEdit<'a> {
|
||||
value: &'a mut usize,
|
||||
min: Option<usize>,
|
||||
max: Option<usize>,
|
||||
enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl<'a> NumberEdit<'a> {
|
||||
pub fn new(value: &'a mut usize) -> Self {
|
||||
pub trait Number: PrimInt + Display + FromStr + Send + Sync + 'static {}
|
||||
impl<T: PrimInt + Display + FromStr + Send + Sync + 'static> Number for T {}
|
||||
|
||||
pub struct NumberEdit<'a, T: Number> {
|
||||
value: &'a mut T,
|
||||
increment: T,
|
||||
min: Option<T>,
|
||||
max: Option<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Number> NumberEdit<'a, T> {
|
||||
pub fn new(value: &'a mut T) -> Self {
|
||||
Self {
|
||||
value,
|
||||
increment: T::one(),
|
||||
min: None,
|
||||
max: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(self, range: impl RangeBounds<usize>) -> Self {
|
||||
pub fn range(self, range: impl RangeBounds<T>) -> Self {
|
||||
let min = match range.start_bound() {
|
||||
Bound::Unbounded => None,
|
||||
Bound::Included(t) => Some(*t),
|
||||
Bound::Excluded(t) => t.checked_add(1),
|
||||
Bound::Excluded(t) => t.checked_add(&self.increment),
|
||||
};
|
||||
let max = match range.end_bound() {
|
||||
Bound::Unbounded => None,
|
||||
Bound::Included(t) => Some(*t),
|
||||
Bound::Excluded(t) => t.checked_sub(1),
|
||||
Bound::Excluded(t) => t.checked_sub(&self.increment),
|
||||
};
|
||||
Self { min, max, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for NumberEdit<'_> {
|
||||
impl<T: Number> Widget for NumberEdit<'_, T> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let (last_value, mut str, focus) = ui.memory(|m| {
|
||||
let (lv, s) = m
|
||||
|
@ -131,7 +146,7 @@ impl Widget for NumberEdit<'_> {
|
|||
str = self.value.to_string();
|
||||
stale = true;
|
||||
}
|
||||
let valid = str.parse().is_ok_and(|v: usize| v == *self.value);
|
||||
let valid = str.parse().is_ok_and(|v: T| v == *self.value);
|
||||
let mut up_pressed = false;
|
||||
let mut down_pressed = false;
|
||||
if focus {
|
||||
|
@ -192,26 +207,30 @@ impl Widget for NumberEdit<'_> {
|
|||
let arrow_middle = (res.rect.min.y + res.rect.max.y) / 2.0;
|
||||
let arrow_bottom = res.rect.max.y + 2.0;
|
||||
|
||||
let mut delta = 0;
|
||||
let mut delta = None;
|
||||
let top_arrow_rect = Rect {
|
||||
min: (arrow_left, arrow_top).into(),
|
||||
max: (arrow_right, arrow_middle).into(),
|
||||
};
|
||||
if draw_arrow(ui, top_arrow_rect, true).clicked_or_dragged() || up_pressed {
|
||||
delta = 1;
|
||||
delta = Some(Direction::Up);
|
||||
}
|
||||
let bottom_arrow_rect = Rect {
|
||||
min: (arrow_left, arrow_middle).into(),
|
||||
max: (arrow_right, arrow_bottom).into(),
|
||||
};
|
||||
if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() || down_pressed {
|
||||
delta = -1;
|
||||
delta = Some(Direction::Down);
|
||||
}
|
||||
|
||||
let in_range =
|
||||
|&val: &usize| self.min.is_none_or(|m| m <= val) && self.max.is_none_or(|m| m >= val);
|
||||
if delta != 0 {
|
||||
if let Some(new_value) = self.value.checked_add_signed(delta).filter(in_range) {
|
||||
|val: &T| self.min.is_none_or(|m| &m <= val) && self.max.is_none_or(|m| &m >= val);
|
||||
if let Some(dir) = delta {
|
||||
let value = match dir {
|
||||
Direction::Up => self.value.checked_add(&self.increment),
|
||||
Direction::Down => self.value.checked_sub(&self.increment),
|
||||
};
|
||||
if let Some(new_value) = value.filter(in_range) {
|
||||
*self.value = new_value;
|
||||
}
|
||||
str = self.value.to_string();
|
||||
|
|
|
@ -62,6 +62,9 @@ impl ObjectWindow {
|
|||
});
|
||||
ui.section("Properties", |ui| {
|
||||
let object = self.objects.borrow().read::<[u16; 4]>(self.index);
|
||||
let mut x = ((object[0] & 0x3ff) << 6 >> 6) as i16;
|
||||
let mut parallax = ((object[1] & 0x3ff) << 6 >> 6) as i16;
|
||||
let mut y = ((object[2] & 0x0ff) << 8 >> 8) as i16;
|
||||
let (mut char_index, mut vflip, mut hflip, palette_index) =
|
||||
utils::parse_cell(object[3]);
|
||||
TableBuilder::new(ui)
|
||||
|
@ -91,6 +94,33 @@ impl ObjectWindow {
|
|||
);
|
||||
});
|
||||
});
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label("X");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add_enabled(false, NumberEdit::new(&mut x).range(-512..512));
|
||||
});
|
||||
});
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label("Y");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add_enabled(false, NumberEdit::new(&mut y).range(-8..=224));
|
||||
});
|
||||
});
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label("Parallax");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add_enabled(
|
||||
false,
|
||||
NumberEdit::new(&mut parallax).range(-512..512),
|
||||
);
|
||||
});
|
||||
});
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
let checkbox = Checkbox::new(&mut hflip, "H-flip");
|
||||
|
|
Loading…
Reference in New Issue