Comments: an opacity control in the quick bar (per tool, 10-100 %)

The comment tools' quick bar had colour and line thickness but no opacity,
though comments already carry it (/CA) and the Properties dialog edits it.
An Opacity slider joins them for every tool; set_opacity clamps to 10-100 %
so a comment can't be made invisible. Test: a strikeout made at 50 % carries
/CA 0.5. Parity comment.properties-bar: shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-05 18:22:52 -04:00
parent c02e1e91c2
commit b4e1d488de
3 changed files with 50 additions and 4 deletions

View File

@ -275,6 +275,13 @@ impl CommentPrefs {
}
}
/// The tool's opacity, 10–100 % (an invisible comment can't be found again).
pub fn set_opacity(&mut self, tool: CommentTool, o: f64) {
if let Some((_, s)) = self.styles.iter_mut().find(|(t, _)| *t == tool) {
s.opacity = if o.is_finite() { o.clamp(0.1, 1.0) } else { 1.0 };
}
}
pub fn set_style(&mut self, tool: CommentTool, style: Style) {
if let Some((_, s)) = self.styles.iter_mut().find(|(t, _)| *t == tool) {
*s = style;
@ -1253,7 +1260,7 @@ pub fn swatch_grid(ui: &mut egui::Ui, current: Option<Rgb>) -> Option<Rgb> {
picked
}
/// The comment tools' extra quick-bar controls: pin, colour and thickness.
/// The comment tools' extra quick-bar controls: pin, colour, opacity and thickness.
pub(crate) fn quick_bar_controls(ui: &mut egui::Ui, tool: CommentTool, prefs: &mut CommentPrefs) {
let style = prefs.style(tool);
if icons::button(ui, "pin", 32.0, prefs.pinned, if prefs.pinned { "Keep tool selected: on" } else { "Keep tool selected" }).clicked() {
@ -1269,6 +1276,15 @@ pub(crate) fn quick_bar_controls(ui: &mut egui::Ui, tool: CommentTool, prefs: &m
ui.close();
}
});
let resp = icons::button(ui, "blend", 32.0, false, "Opacity");
egui::Popup::menu(&resp).align(egui::RectAlign::RIGHT_START).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
ui.set_min_width(180.0);
let mut percent = (style.opacity * 100.0).round();
ui.label(egui::RichText::new("Opacity").font(theme::semibold(12.0)));
if ui.add(egui::Slider::new(&mut percent, 10.0..=100.0).step_by(5.0).suffix(" %")).changed() {
prefs.set_opacity(tool, percent / 100.0);
}
});
if tool.has_width() {
let resp = icons::button(ui, "sliders-horizontal", 32.0, false, "Line thickness");
egui::Popup::menu(&resp).align(egui::RectAlign::RIGHT_START).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {

View File

@ -568,3 +568,33 @@ fn the_squiggly_tool_marks_selected_text() {
assert!(printcraft_ui_egui::comments::GROUPS[1].contains(&printcraft_ui_egui::comments::CommentTool::Squiggly));
assert_eq!(printcraft_ui_egui::comments::tool_for(&c[0]), Some(printcraft_ui_egui::comments::CommentTool::Squiggly));
}
#[test]
fn the_opacity_set_for_a_tool_goes_into_its_new_comments() {
use printcraft_ui_egui::comments::CommentTool;
let mut h = harness(|app| app.comment_prefs.set_opacity(CommentTool::StrikeOut, 0.5));
// Clamped: an invisible comment can't be found again.
h.state_mut().comment_prefs.set_opacity(CommentTool::Underline, 0.0);
assert!((h.state().comment_prefs.style(CommentTool::Underline).opacity - 0.1).abs() < 1e-9);
h.state_mut().views[0].select_text(0, 4, 8);
assert!(h.state_mut().execute("comment.strikeout"));
h.run_steps(2);
let c = comments(&h);
assert_eq!(c.len(), 1, "{c:?}");
// The new comment carries /CA 0.5 (read back from the document's bytes).
let bytes = h.state().session.get(h.state().views[0].id).unwrap().bytes.as_ref().clone();
let doc = printcraft_cos::Document::open(std::sync::Arc::new(bytes)).unwrap();
let opacities: Vec<f64> = doc
.object_numbers()
.into_iter()
.filter_map(|n| doc.try_get(n).ok())
.filter_map(|o| o.as_dict().cloned())
.filter(|d| d.name(b"Subtype") == Some(b"StrikeOut"))
.filter_map(|d| d.get(b"CA").and_then(|v| v.as_f64()))
.collect();
assert_eq!(opacities, [0.5]);
// The quick bar offers the control.
h.state_mut().set_option("quick", "strikeout").unwrap();
h.run_steps(2);
h.get_by_label("Opacity");
}

View File

@ -3767,11 +3767,11 @@ title = "Comment properties bar"
area = "E"
tier = "P1"
milestone = "M5"
status = "partial"
status = "shipped"
commands = ["comment.note"]
tools = ["comment_edit"]
evidence = ["crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance"]
notes = "quick-bar colour swatches and line thickness per tool, colour from the context menu; no opacity control in the UI yet"
evidence = ["crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance", "crates/ui-egui/tests/comments.rs::the_opacity_set_for_a_tool_goes_into_its_new_comments"]
notes = "quick-bar colour swatches, opacity (10–100 %) and line thickness per tool; colour from the context menu"
[[feature]]
id = "comment.lock-comment"