Skip to content

Commit df2c16e

Browse files
authored
Add anchored text rotation method, and clarify related docs (#7130)
Add a helper method to perform rotation about a specified anchor. * Closes #7051
1 parent f33ff2c commit df2c16e

File tree

3 files changed

+116
-5
lines changed

3 files changed

+116
-5
lines changed

crates/egui_demo_lib/src/demo/misc_demo_window.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::{Demo, View};
22

33
use egui::{
4-
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense,
5-
Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
4+
vec2, Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize,
5+
RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
66
};
77

88
/// Showcase some ui code
@@ -16,6 +16,7 @@ pub struct MiscDemoWindow {
1616
custom_collapsing_header: CustomCollapsingHeader,
1717
tree: Tree,
1818
box_painting: BoxPainting,
19+
text_rotation: TextRotation,
1920

2021
dummy_bool: bool,
2122
dummy_usize: usize,
@@ -32,6 +33,7 @@ impl Default for MiscDemoWindow {
3233
custom_collapsing_header: Default::default(),
3334
tree: Tree::demo(),
3435
box_painting: Default::default(),
36+
text_rotation: Default::default(),
3537

3638
dummy_bool: false,
3739
dummy_usize: 0,
@@ -79,6 +81,10 @@ impl View for MiscDemoWindow {
7981
});
8082
});
8183

84+
CollapsingHeader::new("Text rotation")
85+
.default_open(false)
86+
.show(ui, |ui| self.text_rotation.ui(ui));
87+
8288
CollapsingHeader::new("Colors")
8389
.default_open(false)
8490
.show(ui, |ui| {
@@ -729,3 +735,95 @@ fn text_layout_demo(ui: &mut Ui) {
729735

730736
ui.label(job);
731737
}
738+
739+
// ----------------------------------------------------------------------------
740+
741+
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
742+
#[cfg_attr(feature = "serde", serde(default))]
743+
struct TextRotation {
744+
size: Vec2,
745+
angle: f32,
746+
align: egui::Align2,
747+
}
748+
749+
impl Default for TextRotation {
750+
fn default() -> Self {
751+
Self {
752+
size: vec2(200.0, 200.0),
753+
angle: 0.0,
754+
align: egui::Align2::LEFT_TOP,
755+
}
756+
}
757+
}
758+
759+
impl TextRotation {
760+
pub fn ui(&mut self, ui: &mut Ui) {
761+
ui.add(Slider::new(&mut self.angle, 0.0..=2.0 * std::f32::consts::PI).text("angle"));
762+
763+
let default_color = if ui.visuals().dark_mode {
764+
Color32::LIGHT_GRAY
765+
} else {
766+
Color32::DARK_GRAY
767+
};
768+
769+
let aligns = [
770+
(Align2::LEFT_TOP, "LEFT_TOP"),
771+
(Align2::LEFT_CENTER, "LEFT_CENTER"),
772+
(Align2::LEFT_BOTTOM, "LEFT_BOTTOM"),
773+
(Align2::CENTER_TOP, "CENTER_TOP"),
774+
(Align2::CENTER_CENTER, "CENTER_CENTER"),
775+
(Align2::CENTER_BOTTOM, "CENTER_BOTTOM"),
776+
(Align2::RIGHT_TOP, "RIGHT_TOP"),
777+
(Align2::RIGHT_CENTER, "RIGHT_CENTER"),
778+
(Align2::RIGHT_BOTTOM, "RIGHT_BOTTOM"),
779+
];
780+
781+
ComboBox::new("anchor", "Anchor")
782+
.selected_text(aligns.iter().find(|(a, _)| *a == self.align).unwrap().1)
783+
.show_ui(ui, |ui| {
784+
for (align2, name) in &aligns {
785+
ui.selectable_value(&mut self.align, *align2, *name);
786+
}
787+
});
788+
789+
ui.horizontal_wrapped(|ui| {
790+
let (response, painter) = ui.allocate_painter(self.size, Sense::empty());
791+
let rect = response.rect;
792+
793+
let start_pos = self.size / 2.0;
794+
795+
let s = ui.ctx().fonts(|f| {
796+
let mut t = egui::Shape::text(
797+
f,
798+
rect.min + start_pos,
799+
egui::Align2::LEFT_TOP,
800+
"sample_text",
801+
egui::FontId::new(12.0, egui::FontFamily::Proportional),
802+
default_color,
803+
);
804+
805+
if let egui::epaint::Shape::Text(ts) = &mut t {
806+
let new = ts.clone().with_angle_and_anchor(self.angle, self.align);
807+
*ts = new;
808+
};
809+
810+
t
811+
});
812+
813+
if let egui::epaint::Shape::Text(ts) = &s {
814+
let align_pt =
815+
rect.min + start_pos + self.align.pos_in_rect(&ts.galley.rect).to_vec2();
816+
painter.circle(align_pt, 2.0, Color32::RED, (0.0, Color32::RED));
817+
};
818+
819+
painter.rect(
820+
rect,
821+
0.0,
822+
default_color.gamma_multiply(0.3),
823+
(0.0, Color32::BLACK),
824+
egui::StrokeKind::Middle,
825+
);
826+
painter.add(s);
827+
});
828+
}
829+
}
Lines changed: 2 additions & 2 deletions
Loading

crates/epaint/src/shapes/text_shape.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::sync::Arc;
22

3+
use emath::{Align2, Rot2};
4+
35
use crate::*;
46

57
/// How to paint some text on screen.
@@ -78,14 +80,25 @@ impl TextShape {
7880
self
7981
}
8082

81-
/// Rotate text by this many radians clockwise.
83+
/// Set text rotation to `angle` radians clockwise.
8284
/// The pivot is `pos` (the upper left corner of the text).
8385
#[inline]
8486
pub fn with_angle(mut self, angle: f32) -> Self {
8587
self.angle = angle;
8688
self
8789
}
8890

91+
/// Set the text rotation to the `angle` radians clockwise.
92+
/// The pivot is determined by the given `anchor` point on the text bounding box.
93+
#[inline]
94+
pub fn with_angle_and_anchor(mut self, angle: f32, anchor: Align2) -> Self {
95+
self.angle = angle;
96+
let a0 = anchor.pos_in_rect(&self.galley.rect).to_vec2();
97+
let a1 = Rot2::from_angle(angle) * a0;
98+
self.pos += a0 - a1;
99+
self
100+
}
101+
89102
/// Render text with this opacity in gamma space
90103
#[inline]
91104
pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {

0 commit comments

Comments
 (0)