diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index edb19c3e..99d85aee 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -1,8 +1,8 @@ use super::{Demo, View}; use egui::{ - vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense, - Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, + vec2, Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize, + RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, }; /// Showcase some ui code @@ -16,6 +16,7 @@ pub struct MiscDemoWindow { custom_collapsing_header: CustomCollapsingHeader, tree: Tree, box_painting: BoxPainting, + text_rotation: TextRotation, dummy_bool: bool, dummy_usize: usize, @@ -32,6 +33,7 @@ impl Default for MiscDemoWindow { custom_collapsing_header: Default::default(), tree: Tree::demo(), box_painting: Default::default(), + text_rotation: Default::default(), dummy_bool: false, dummy_usize: 0, @@ -79,6 +81,10 @@ impl View for MiscDemoWindow { }); }); + CollapsingHeader::new("Text rotation") + .default_open(false) + .show(ui, |ui| self.text_rotation.ui(ui)); + CollapsingHeader::new("Colors") .default_open(false) .show(ui, |ui| { @@ -729,3 +735,95 @@ fn text_layout_demo(ui: &mut Ui) { ui.label(job); } + +// ---------------------------------------------------------------------------- + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +struct TextRotation { + size: Vec2, + angle: f32, + align: egui::Align2, +} + +impl Default for TextRotation { + fn default() -> Self { + Self { + size: vec2(200.0, 200.0), + angle: 0.0, + align: egui::Align2::LEFT_TOP, + } + } +} + +impl TextRotation { + pub fn ui(&mut self, ui: &mut Ui) { + ui.add(Slider::new(&mut self.angle, 0.0..=2.0 * std::f32::consts::PI).text("angle")); + + let default_color = if ui.visuals().dark_mode { + Color32::LIGHT_GRAY + } else { + Color32::DARK_GRAY + }; + + let aligns = [ + (Align2::LEFT_TOP, "LEFT_TOP"), + (Align2::LEFT_CENTER, "LEFT_CENTER"), + (Align2::LEFT_BOTTOM, "LEFT_BOTTOM"), + (Align2::CENTER_TOP, "CENTER_TOP"), + (Align2::CENTER_CENTER, "CENTER_CENTER"), + (Align2::CENTER_BOTTOM, "CENTER_BOTTOM"), + (Align2::RIGHT_TOP, "RIGHT_TOP"), + (Align2::RIGHT_CENTER, "RIGHT_CENTER"), + (Align2::RIGHT_BOTTOM, "RIGHT_BOTTOM"), + ]; + + ComboBox::new("anchor", "Anchor") + .selected_text(aligns.iter().find(|(a, _)| *a == self.align).unwrap().1) + .show_ui(ui, |ui| { + for (align2, name) in &aligns { + ui.selectable_value(&mut self.align, *align2, *name); + } + }); + + ui.horizontal_wrapped(|ui| { + let (response, painter) = ui.allocate_painter(self.size, Sense::empty()); + let rect = response.rect; + + let start_pos = self.size / 2.0; + + let s = ui.ctx().fonts(|f| { + let mut t = egui::Shape::text( + f, + rect.min + start_pos, + egui::Align2::LEFT_TOP, + "sample_text", + egui::FontId::new(12.0, egui::FontFamily::Proportional), + default_color, + ); + + if let egui::epaint::Shape::Text(ts) = &mut t { + let new = ts.clone().with_angle_and_anchor(self.angle, self.align); + *ts = new; + }; + + t + }); + + if let egui::epaint::Shape::Text(ts) = &s { + let align_pt = + rect.min + start_pos + self.align.pos_in_rect(&ts.galley.rect).to_vec2(); + painter.circle(align_pt, 2.0, Color32::RED, (0.0, Color32::RED)); + }; + + painter.rect( + rect, + 0.0, + default_color.gamma_multiply(0.3), + (0.0, Color32::BLACK), + egui::StrokeKind::Middle, + ); + painter.add(s); + }); + } +} diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 267fa6be..f11c5d8a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:880344367ed65f83898ceca4843b1b6259d1690242ced0d29ac8dc48100a8faa -size 62956 +oid sha256:116a53258be27d9c7c56538e5f83202ea731f19887fabadc0449d24fde4d80d9 +size 64494 diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 4ea0ac35..bf9db964 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use emath::{Align2, Rot2}; + use crate::*; /// How to paint some text on screen. @@ -78,7 +80,7 @@ impl TextShape { self } - /// Rotate text by this many radians clockwise. + /// Set text rotation to `angle` radians clockwise. /// The pivot is `pos` (the upper left corner of the text). #[inline] pub fn with_angle(mut self, angle: f32) -> Self { @@ -86,6 +88,17 @@ impl TextShape { self } + /// Set the text rotation to the `angle` radians clockwise. + /// The pivot is determined by the given `anchor` point on the text bounding box. + #[inline] + pub fn with_angle_and_anchor(mut self, angle: f32, anchor: Align2) -> Self { + self.angle = angle; + let a0 = anchor.pos_in_rect(&self.galley.rect).to_vec2(); + let a1 = Rot2::from_angle(angle) * a0; + self.pos += a0 - a1; + self + } + /// Render text with this opacity in gamma space #[inline] pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {