View cube face labels are larger: one font size for all six, the widest (BOTTOM) spanning 90% of its face, instead of a fixed 9.5 px that left the names small on the cube. Tests: BOTTOM spans 90% of the face seen straight on, and every label stays within its face from any direction
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
feaed2e3f6
commit
ff1453f868
1 file changed
+44
-6
@@ -2102,6 +2102,8 @@ fn view_cube(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
|||||||
let tip = at + vec2((d.dot(wr) * CUBE_SCALE * 2.6) as f32, -(d.dot(wu) * CUBE_SCALE * 2.6) as f32);
|
let tip = at + vec2((d.dot(wr) * CUBE_SCALE * 2.6) as f32, -(d.dot(wu) * CUBE_SCALE * 2.6) as f32);
|
||||||
painter.line_segment([at, tip], Stroke::new(2.0, Color32::from_rgb(col.0, col.1, col.2)));
|
painter.line_segment([at, tip], Stroke::new(2.0, Color32::from_rgb(col.0, col.1, col.2)));
|
||||||
}
|
}
|
||||||
|
let labels: Vec<String> = targets.iter().filter(|tg| !tg.corner).map(|tg| tg.name.to_uppercase()).collect();
|
||||||
|
let size = cube_label_size(ui.ctx(), &labels);
|
||||||
// Back to front: the faces and facets turned toward the camera.
|
// Back to front: the faces and facets turned toward the camera.
|
||||||
for (i, pts) in shapes.iter().rev() {
|
for (i, pts) in shapes.iter().rev() {
|
||||||
let tg = &targets[*i];
|
let tg = &targets[*i];
|
||||||
@@ -2117,7 +2119,8 @@ fn view_cube(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
|||||||
// Fades out as the face turns edge-on.
|
// Fades out as the face turns edge-on.
|
||||||
let ink = (n.dot(b) / 0.3).clamp(0.0, 1.0) as f32;
|
let ink = (n.dot(b) / 0.3).clamp(0.0, 1.0) as f32;
|
||||||
if !tg.corner && ink > 0.05 {
|
if !tg.corner && ink > 0.05 {
|
||||||
painter.add(cube_label(ui.ctx(), &tg.name.to_uppercase(), n, (r, u), c, Color32::from_rgb(40, 44, 52).gamma_multiply(ink)));
|
let color = Color32::from_rgb(40, 44, 52).gamma_multiply(ink);
|
||||||
|
painter.add(cube_label(ui.ctx(), &tg.name.to_uppercase(), size, n, (r, u), c, color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::scenario::publish_count("cube_highlights", f64::from(u8::from(hovered.is_some())));
|
crate::scenario::publish_count("cube_highlights", f64::from(u8::from(hovered.is_some())));
|
||||||
@@ -2208,10 +2211,24 @@ fn cube_label_axes(n: Vec3) -> (Vec3, Vec3) {
|
|||||||
if n.z.abs() > 0.5 { (Vec3::X, Vec3::Y * n.z.signum()) } else { (Vec3::Z.cross(n), Vec3::Z) }
|
if n.z.abs() > 0.5 { (Vec3::X, Vec3::Y * n.z.signum()) } else { (Vec3::Z.cross(n), Vec3::Z) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A face's label printed on the face: laid out flat, then mapped through the cube's projection,
|
/// How much of a face the widest face label spans.
|
||||||
/// so it turns and foreshortens with the face.
|
const CUBE_LABEL_WIDTH: f32 = 0.9;
|
||||||
fn cube_label(ctx: &egui::Context, text: &str, n: Vec3, ru: (Vec3, Vec3), c: Pos2, color: Color32) -> Shape {
|
|
||||||
let galley = ctx.fonts_mut(|f| f.layout(text.to_string(), FontId::proportional(9.5), color, f32::INFINITY));
|
/// The face labels' font size: one size for all, the widest of `labels` spanning
|
||||||
|
/// `CUBE_LABEL_WIDTH` of a face.
|
||||||
|
fn cube_label_size(ctx: &egui::Context, labels: &[String]) -> f32 {
|
||||||
|
let probe = 10.0;
|
||||||
|
let widest = labels
|
||||||
|
.iter()
|
||||||
|
.map(|s| ctx.fonts_mut(|f| f.layout_no_wrap(s.clone(), FontId::proportional(probe), Color32::WHITE).size().x))
|
||||||
|
.fold(0.0, f32::max);
|
||||||
|
if widest > 0.0 { probe * CUBE_LABEL_WIDTH * 2.0 * CUBE_SCALE as f32 / widest } else { probe }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A face's label printed on the face at `size`: laid out flat, then mapped through the cube's
|
||||||
|
/// projection, so it turns and foreshortens with the face.
|
||||||
|
fn cube_label(ctx: &egui::Context, text: &str, size: f32, n: Vec3, ru: (Vec3, Vec3), c: Pos2, color: Color32) -> Shape {
|
||||||
|
let galley = ctx.fonts_mut(|f| f.layout(text.to_string(), FontId::proportional(size), color, f32::INFINITY));
|
||||||
let options = egui::epaint::TessellationOptions { round_text_to_pixels: false, ..Default::default() };
|
let options = egui::epaint::TessellationOptions { round_text_to_pixels: false, ..Default::default() };
|
||||||
let mut tess = egui::epaint::Tessellator::new(ctx.pixels_per_point(), options, ctx.fonts(|f| f.font_image_size()), vec![]);
|
let mut tess = egui::epaint::Tessellator::new(ctx.pixels_per_point(), options, ctx.fonts(|f| f.font_image_size()), vec![]);
|
||||||
let mut mesh = egui::Mesh::default();
|
let mut mesh = egui::Mesh::default();
|
||||||
@@ -2826,6 +2843,26 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn face_labels(targets: &[CubeTarget]) -> Vec<String> {
|
||||||
|
targets.iter().filter(|t| !t.corner).map(|t| t.name.to_uppercase()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The widest label spans 90% of its face, looking straight at it.
|
||||||
|
#[test]
|
||||||
|
fn view_cube_widest_label_spans_most_of_its_face() {
|
||||||
|
let ctx = egui::Context::default();
|
||||||
|
let mut span = 0.0f32;
|
||||||
|
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||||
|
let size = cube_label_size(ui.ctx(), &face_labels(&cube_targets()));
|
||||||
|
let front = Vec3::new(0.0, -1.0, 0.0);
|
||||||
|
let (r, u, _) = basis_from(front);
|
||||||
|
let pts = drawn_points(vec![cube_label(ui.ctx(), "BOTTOM", size, front, (r, u), pos2(100.0, 100.0), Color32::BLACK)]);
|
||||||
|
let (lo, hi) = pts.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| (lo.min(p.x), hi.max(p.x)));
|
||||||
|
span = (hi - lo) / (2.0 * CUBE_SCALE as f32);
|
||||||
|
});
|
||||||
|
assert!((span - CUBE_LABEL_WIDTH).abs() < 0.05, "BOTTOM spans {span} of the face");
|
||||||
|
}
|
||||||
|
|
||||||
/// From any direction, each label is printed within its face.
|
/// From any direction, each label is printed within its face.
|
||||||
#[test]
|
#[test]
|
||||||
fn view_cube_labels_stay_on_their_faces() {
|
fn view_cube_labels_stay_on_their_faces() {
|
||||||
@@ -2833,6 +2870,7 @@ mod tests {
|
|||||||
let (mut worst, mut drawn) = (f32::NEG_INFINITY, 0);
|
let (mut worst, mut drawn) = (f32::NEG_INFINITY, 0);
|
||||||
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
|
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||||
let targets = cube_targets();
|
let targets = cube_targets();
|
||||||
|
let size = cube_label_size(ui.ctx(), &face_labels(&targets));
|
||||||
let c = pos2(100.0, 100.0);
|
let c = pos2(100.0, 100.0);
|
||||||
for dir in orbit_directions().step_by(7) {
|
for dir in orbit_directions().step_by(7) {
|
||||||
let (r, u, b) = basis_from(dir);
|
let (r, u, b) = basis_from(dir);
|
||||||
@@ -2841,7 +2879,7 @@ mod tests {
|
|||||||
if tg.corner {
|
if tg.corner {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for p in drawn_points(vec![cube_label(ui.ctx(), &tg.name.to_uppercase(), tg.normal(), (r, u), c, Color32::BLACK)]) {
|
for p in drawn_points(vec![cube_label(ui.ctx(), &tg.name.to_uppercase(), size, tg.normal(), (r, u), c, Color32::BLACK)]) {
|
||||||
let outside = if point_in_poly(p, &pts) { 0.0 } else { poly_distance(p, &pts) };
|
let outside = if point_in_poly(p, &pts) { 0.0 } else { poly_distance(p, &pts) };
|
||||||
worst = worst.max(outside);
|
worst = worst.max(outside);
|
||||||
drawn += 1;
|
drawn += 1;
|
||||||
|
|||||||
Reference in new issue
Block a user