VIP inspection tooling #4
			
				
			
		
		
		
	| 
						 | 
					@ -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