diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index 9d495fe6..28f468f2 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -416,6 +416,7 @@ struct Widgets { count: usize, radio: usize, slider_value: f32, + angle: f32, single_line_text_input: String, multiline_text_input: String, } @@ -427,6 +428,7 @@ impl Default for Widgets { radio: 0, count: 0, slider_value: 3.4, + angle: TAU / 8.0, single_line_text_input: "Hello World!".to_owned(), multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(), } @@ -480,6 +482,15 @@ impl Widgets { } } ui.separator(); + { + ui.label("An angle stored as radians, but edited in degrees:"); + ui.horizontal_centered(|ui| { + ui.style_mut().item_spacing.x = 0.0; + ui.drag_angle(&mut self.angle); + ui.label(format!(" = {} radians", self.angle)); + }); + } + ui.separator(); ui.horizontal(|ui| { ui.add(label!("Single line text input:")); diff --git a/egui/src/paint/font.rs b/egui/src/paint/font.rs index 70336fd9..3425e1df 100644 --- a/egui/src/paint/font.rs +++ b/egui/src/paint/font.rs @@ -211,6 +211,7 @@ impl Font { font.add_char(c); } font.add_char(REPLACEMENT_CHAR); + font.add_char('°'); font } diff --git a/egui/src/ui.rs b/egui/src/ui.rs index f1344c5a..1d05472c 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -506,6 +506,22 @@ impl Ui { pub fn separator(&mut self) -> GuiResponse { self.add(Separator::new()) } + + /// Modify an angle. The given angle should be in radians, but is shown to the user in degrees. + /// The angle is NOT wrapped, so the user may select, for instance 720° = 2𝞃 = 4π + pub fn drag_angle(&mut self, radians: &mut f32) -> GuiResponse { + #![allow(clippy::float_cmp)] + + let mut degrees = radians.to_degrees(); + let response = self.add(DragValue::f32(&mut degrees).speed(1.0).suffix("°")); + + // only touch `*radians` if we actually changed the degree value + if degrees != radians.to_degrees() { + *radians = degrees.to_radians(); + } + + response + } } /// # Adding Containers / Sub-uis: diff --git a/egui/src/widgets.rs b/egui/src/widgets.rs index 35e8744c..4e0b9dc5 100644 --- a/egui/src/widgets.rs +++ b/egui/src/widgets.rs @@ -552,11 +552,18 @@ impl Widget for Separator { pub struct DragValue<'a> { value: &'a mut f32, speed: f32, + prefix: String, + suffix: String, } impl<'a> DragValue<'a> { pub fn f32(value: &'a mut f32) -> Self { - DragValue { value, speed: 1.0 } + DragValue { + value, + speed: 1.0, + prefix: Default::default(), + suffix: Default::default(), + } } /// How much the value changes when dragged one point (logical pixel). @@ -564,16 +571,30 @@ impl<'a> DragValue<'a> { self.speed = speed; self } + + /// Show a prefix before the number, e.g. "x: " + pub fn prefix(mut self, prefix: impl ToString) -> Self { + self.prefix = prefix.to_string(); + self + } + + /// Add a suffix to the number, this can be e.g. a unit ("°" or " m") + pub fn suffix(mut self, suffix: impl ToString) -> Self { + self.suffix = suffix.to_string(); + self + } } impl<'a> Widget for DragValue<'a> { fn ui(self, ui: &mut Ui) -> InteractInfo { - let Self { value, speed } = self; - let speed_in_physical_pixels = speed / ui.input().pixels_per_point; - let precision = (1.0 / speed_in_physical_pixels.abs()) - .log10() - .ceil() - .max(0.0) as usize; + let Self { + value, + speed, + prefix, + suffix, + } = self; + let aim_rad = ui.input().physical_pixel_size(); // ui.input().aim_radius(); // TODO + let precision = (aim_rad / speed.abs()).log10().ceil().max(0.0) as usize; let value_text = format_with_minimum_precision(*value, precision); let kb_edit_id = ui.make_position_id().with("edit"); @@ -602,7 +623,7 @@ impl<'a> Widget for DragValue<'a> { } response.into() } else { - let button = Button::new(value_text) + let button = Button::new(format!("{}{}{}", prefix, value_text, suffix)) .sense(Sense::click_and_drag()) .text_style(TextStyle::Monospace); let response = ui.add(button);