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::{
|
use egui::{
|
||||||
ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
|
ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
|
||||||
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
||||||
WidgetText,
|
WidgetText,
|
||||||
};
|
};
|
||||||
|
use num_traits::PrimInt;
|
||||||
|
|
||||||
pub trait UiExt {
|
pub trait UiExt {
|
||||||
fn section(&mut self, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui));
|
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> {
|
enum Direction {
|
||||||
value: &'a mut usize,
|
Up,
|
||||||
min: Option<usize>,
|
Down,
|
||||||
max: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NumberEdit<'a> {
|
pub trait Number: PrimInt + Display + FromStr + Send + Sync + 'static {}
|
||||||
pub fn new(value: &'a mut usize) -> Self {
|
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 {
|
Self {
|
||||||
value,
|
value,
|
||||||
|
increment: T::one(),
|
||||||
min: None,
|
min: None,
|
||||||
max: 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() {
|
let min = match range.start_bound() {
|
||||||
Bound::Unbounded => None,
|
Bound::Unbounded => None,
|
||||||
Bound::Included(t) => Some(*t),
|
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() {
|
let max = match range.end_bound() {
|
||||||
Bound::Unbounded => None,
|
Bound::Unbounded => None,
|
||||||
Bound::Included(t) => Some(*t),
|
Bound::Included(t) => Some(*t),
|
||||||
Bound::Excluded(t) => t.checked_sub(1),
|
Bound::Excluded(t) => t.checked_sub(&self.increment),
|
||||||
};
|
};
|
||||||
Self { min, max, ..self }
|
Self { min, max, ..self }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for NumberEdit<'_> {
|
impl<T: Number> Widget for NumberEdit<'_, T> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let (last_value, mut str, focus) = ui.memory(|m| {
|
let (last_value, mut str, focus) = ui.memory(|m| {
|
||||||
let (lv, s) = m
|
let (lv, s) = m
|
||||||
|
@ -131,7 +146,7 @@ impl Widget for NumberEdit<'_> {
|
||||||
str = self.value.to_string();
|
str = self.value.to_string();
|
||||||
stale = true;
|
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 up_pressed = false;
|
||||||
let mut down_pressed = false;
|
let mut down_pressed = false;
|
||||||
if focus {
|
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_middle = (res.rect.min.y + res.rect.max.y) / 2.0;
|
||||||
let arrow_bottom = 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 {
|
let top_arrow_rect = Rect {
|
||||||
min: (arrow_left, arrow_top).into(),
|
min: (arrow_left, arrow_top).into(),
|
||||||
max: (arrow_right, arrow_middle).into(),
|
max: (arrow_right, arrow_middle).into(),
|
||||||
};
|
};
|
||||||
if draw_arrow(ui, top_arrow_rect, true).clicked_or_dragged() || up_pressed {
|
if draw_arrow(ui, top_arrow_rect, true).clicked_or_dragged() || up_pressed {
|
||||||
delta = 1;
|
delta = Some(Direction::Up);
|
||||||
}
|
}
|
||||||
let bottom_arrow_rect = Rect {
|
let bottom_arrow_rect = Rect {
|
||||||
min: (arrow_left, arrow_middle).into(),
|
min: (arrow_left, arrow_middle).into(),
|
||||||
max: (arrow_right, arrow_bottom).into(),
|
max: (arrow_right, arrow_bottom).into(),
|
||||||
};
|
};
|
||||||
if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() || down_pressed {
|
if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() || down_pressed {
|
||||||
delta = -1;
|
delta = Some(Direction::Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
let in_range =
|
let in_range =
|
||||||
|&val: &usize| self.min.is_none_or(|m| m <= val) && self.max.is_none_or(|m| m >= val);
|
|val: &T| self.min.is_none_or(|m| &m <= val) && self.max.is_none_or(|m| &m >= val);
|
||||||
if delta != 0 {
|
if let Some(dir) = delta {
|
||||||
if let Some(new_value) = self.value.checked_add_signed(delta).filter(in_range) {
|
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;
|
*self.value = new_value;
|
||||||
}
|
}
|
||||||
str = self.value.to_string();
|
str = self.value.to_string();
|
||||||
|
|
|
@ -62,6 +62,9 @@ impl ObjectWindow {
|
||||||
});
|
});
|
||||||
ui.section("Properties", |ui| {
|
ui.section("Properties", |ui| {
|
||||||
let object = self.objects.borrow().read::<[u16; 4]>(self.index);
|
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) =
|
let (mut char_index, mut vflip, mut hflip, palette_index) =
|
||||||
utils::parse_cell(object[3]);
|
utils::parse_cell(object[3]);
|
||||||
TableBuilder::new(ui)
|
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| {
|
body.row(row_height, |mut row| {
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
let checkbox = Checkbox::new(&mut hflip, "H-flip");
|
let checkbox = Checkbox::new(&mut hflip, "H-flip");
|
||||||
|
|
Loading…
Reference in New Issue