From b5e1711a567d5180bb5869403d64ac6ebf3d3100 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Thu, 13 Feb 2025 23:37:40 -0500 Subject: [PATCH] Support signed numbers in number picker --- src/window/utils.rs | 55 ++++++++++++++++++++++++++------------- src/window/vram/object.rs | 30 +++++++++++++++++++++ 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/window/utils.rs b/src/window/utils.rs index 7465794..ec3fe98 100644 --- a/src/window/utils.rs +++ b/src/window/utils.rs @@ -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, add_contents: impl FnOnce(&mut Ui)); @@ -86,37 +91,47 @@ impl UiExt for Ui { } } -pub struct NumberEdit<'a> { - value: &'a mut usize, - min: Option, - max: Option, +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 Number for T {} + +pub struct NumberEdit<'a, T: Number> { + value: &'a mut T, + increment: T, + min: Option, + max: Option, +} + +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) -> Self { + pub fn range(self, range: impl RangeBounds) -> 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 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(); diff --git a/src/window/vram/object.rs b/src/window/vram/object.rs index 23dbb60..756aff0 100644 --- a/src/window/vram/object.rs +++ b/src/window/vram/object.rs @@ -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");