G4: reuse Character panel Shared/Metric for mixed paragraph fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
nrq6993-a11y
2026-10-21 23:38:03 +07:00
committed by bflatastic
co-authored by Cursor
parent c7df7a56e8
commit cc4836abb7
2 changed files with 15 additions and 57 deletions
+4 -4
View File
@@ -68,7 +68,7 @@ fn shared_effective(app: &VectorcraftApp, f: impl Fn(&CharStyle) -> f64) -> Opti
shared_styles(app, |t, style| t.effective_char_style(style).map(|s| Metric(f(&s)))).map(|v| v.0)
}
struct Metric(f64);
pub(crate) struct Metric(pub(crate) f64);
impl PartialEq for Metric {
fn eq(&self, other: &Self) -> bool {
@@ -109,21 +109,21 @@ fn shared_styles<T: PartialEq>(app: &VectorcraftApp, f: impl Fn(&vectorcraft_doc
}
/// The value a run of styles share so far ([`shared`]).
enum Shared<T> {
pub(crate) enum Shared<T> {
None,
Same(T),
Differ,
}
impl<T: PartialEq> Shared<T> {
fn add(&mut self, v: T) {
pub(crate) fn add(&mut self, v: T) {
match self {
Shared::None => *self = Shared::Same(v),
Shared::Same(x) if *x != v => *self = Shared::Differ,
_ => {}
}
}
fn value(self) -> Option<T> {
pub(crate) fn value(self) -> Option<T> {
match self {
Shared::Same(v) => Some(v),
_ => None,
+11 -53
View File
@@ -7,7 +7,7 @@ use egui::{Ui, vec2};
use serde_json::{Value, json};
use vectorcraft_doc::{Composer, HyphenationOptions, Justify, NodeKind, ParaDirection, ParaStyle};
use super::character::text_style;
use super::character::{Metric, Shared, text_style};
use super::{pstate, set_pstate};
use crate::VectorcraftApp;
use crate::theme::Tokens;
@@ -142,79 +142,37 @@ fn collect_text_alignment(text: &vectorcraft_doc::TextObject, range: Option<(usi
record(index, &plain, start, end) // Includes empty text and the paragraph after a final newline.
}
/// Compare paragraph distances with the same tolerance as Character dimensional fields.
struct ParaMetric(f64);
impl PartialEq for ParaMetric {
fn eq(&self, other: &Self) -> bool {
(self.0 - other.0).abs() <= 1e-9 * self.0.abs().max(other.0.abs()).max(1.0)
}
}
enum ParaShared<T> {
None,
Same(T),
Differ,
}
impl<T: PartialEq> ParaShared<T> {
fn add(&mut self, v: T) {
match self {
Self::None => *self = Self::Same(v),
Self::Same(old) if *old == v => {}
_ => *self = Self::Differ,
}
}
fn value(self) -> Option<T> {
match self {
Self::Same(v) => Some(v),
_ => None,
}
}
}
/// Visit every paragraph the Paragraph panel acts on; stop early when `f` returns false.
fn visit_paragraphs(app: &VectorcraftApp, mut f: impl FnMut(&ParaStyle) -> bool) -> bool {
let Some(st) = app.session.active() else { return false };
/// Visit every paragraph the Paragraph panel acts on.
fn visit_paragraphs(app: &VectorcraftApp, mut f: impl FnMut(&ParaStyle)) {
let Some(st) = app.session.active() else { return };
if let Some((id, a, b)) = super::character::text_editing(app)
&& let Some(NodeKind::Text(text)) = st.doc.node(id).map(|n| &n.kind)
{
let range = text.paragraphs_in(a, b);
for i in range {
if !f(text.para_at(i)) {
return false;
}
f(text.para_at(i));
}
return true;
return;
}
let mut ok = true;
for node in st.selection.objects.iter().filter_map(|id| st.doc.node(*id)) {
node.walk(&mut |c| {
if let NodeKind::Text(t) = &c.kind {
for i in 0..t.paragraph_count() {
if !f(t.para_at(i)) {
ok = false;
}
f(t.para_at(i));
}
}
});
if !ok {
return false;
}
}
ok
}
/// A distance shared by every paragraph in scope, or `None` when they differ (blank spin field).
fn shared_para_field(app: &VectorcraftApp, field: impl Fn(&ParaStyle) -> f64) -> Option<f64> {
let mut acc = ParaShared::None;
let mut acc = Shared::None;
let mut any = false;
let record = |p: &ParaStyle| {
visit_paragraphs(app, |p| {
any = true;
acc.add(ParaMetric(field(p)));
true
};
visit_paragraphs(app, record);
acc.add(Metric(field(p)));
});
if !any {
return text_style(app).map(|(_, p)| field(&p));
}