mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 11:36:58 -06:00
refactor: split complex functions into helpers (S3776)
Lower the cognitive complexity of segment_at, apply_mmco, derive_amvp, plotted_extent and look_with_def by moving their per-segment, per-MMCO, per-neighbour, per-stack and per-preset-style steps into helpers. The expressions and their evaluation order are unchanged.
This commit is contained in:
@@ -658,6 +658,62 @@ pub fn vertex_at(doc: &deckcraft_model::Presentation, sel: &crate::Selection, sh
|
||||
None
|
||||
}
|
||||
|
||||
/// The point at parameter `t` of segment `si`, scaled by `scale` into the shape's box.
|
||||
fn segment_point(segs: &[Seg], si: usize, p0: Point, t: f64, scale: (f64, f64)) -> Option<Point> {
|
||||
let q = match segs.get(si)? {
|
||||
Seg::L(p1) => p0.lerp(*p1, t),
|
||||
Seg::Q(c, p1) => p0.lerp(*c, t).lerp(c.lerp(*p1, t), t),
|
||||
Seg::C(c1, c2, p1) => {
|
||||
let (a, b, c) = (p0.lerp(*c1, t), c1.lerp(*c2, t), c2.lerp(*p1, t));
|
||||
a.lerp(b, t).lerp(b.lerp(c, t), t)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(Point::new(q.x * scale.0, q.y * scale.1))
|
||||
}
|
||||
|
||||
/// The vertex that ends segment `si` (a closing edge ends on the start).
|
||||
fn segment_end_vertex(segs: &[Seg], vs: &[usize], si: usize) -> Option<usize> {
|
||||
if closes_onto_start(segs, si) {
|
||||
let start = segs.get(..si).and_then(|s| s.iter().rposition(|x| matches!(x, Seg::M(_))));
|
||||
start.and_then(|m| vs.iter().position(|v| *v == m))
|
||||
} else {
|
||||
vs.iter().position(|v| *v == si)
|
||||
}
|
||||
}
|
||||
|
||||
/// The distance and `t` of the point on segment `si` nearest `local`, if it lies within `tol`.
|
||||
fn nearest_on_segment(segs: &[Seg], si: usize, p0: Point, scale: (f64, f64), local: Point, tol: f64) -> Option<(f64, f64)> {
|
||||
let at = |t: f64| segment_point(segs, si, p0, t, scale);
|
||||
let mut near: Option<(f64, f64)> = None;
|
||||
for k in 1..32 {
|
||||
let t = k as f64 / 32.0;
|
||||
if let Some(q) = at(t) {
|
||||
let d = (q - local).hypot();
|
||||
if d <= tol && near.is_none_or(|n| d < n.0) {
|
||||
near = Some((d, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (mut d, mut t) = near?;
|
||||
// Narrow `t` down to the nearest place on the segment, where Add Point puts the point.
|
||||
let mut step = 1.0 / 32.0;
|
||||
for _ in 0..4 {
|
||||
step /= 8.0;
|
||||
let c = t;
|
||||
for k in -8..=8 {
|
||||
let u = (c + f64::from(k) * step).clamp(0.0, 1.0);
|
||||
if let Some(q) = at(u) {
|
||||
let e = (q - local).hypot();
|
||||
if e < d {
|
||||
(d, t) = (e, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((d, t))
|
||||
}
|
||||
|
||||
/// The segment of `sh`'s outline near slide point `p`: (path, index of the vertex ending it, t).
|
||||
pub fn segment_at(doc: &deckcraft_model::Presentation, sel: &crate::Selection, sh: &Shape, p: Point, tol: f64) -> Option<(usize, usize, f64)> {
|
||||
let Geom::Custom { paths } = &sh.geom else { return None };
|
||||
@@ -665,57 +721,13 @@ pub fn segment_at(doc: &deckcraft_model::Presentation, sel: &crate::Selection, s
|
||||
let local = x.affine().inverse() * p;
|
||||
let mut best: Option<(f64, usize, usize, f64)> = None;
|
||||
for (pi, cp) in paths.iter().enumerate() {
|
||||
let (sx, sy) = (if cp.w > 0.0 { x.w / cp.w } else { 1.0 }, if cp.h > 0.0 { x.h / cp.h } else { 1.0 });
|
||||
let scale = (if cp.w > 0.0 { x.w / cp.w } else { 1.0 }, if cp.h > 0.0 { x.h / cp.h } else { 1.0 });
|
||||
let segs = parse(&cp.d);
|
||||
let vs = vertices(&segs);
|
||||
for si in 0..segs.len() {
|
||||
let Some(p0) = seg_start(&segs, si) else { continue };
|
||||
let at = |t: f64| -> Option<Point> {
|
||||
let q = match segs.get(si)? {
|
||||
Seg::L(p1) => p0.lerp(*p1, t),
|
||||
Seg::Q(c, p1) => p0.lerp(*c, t).lerp(c.lerp(*p1, t), t),
|
||||
Seg::C(c1, c2, p1) => {
|
||||
let (a, b, c) = (p0.lerp(*c1, t), c1.lerp(*c2, t), c2.lerp(*p1, t));
|
||||
a.lerp(b, t).lerp(b.lerp(c, t), t)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(Point::new(q.x * sx, q.y * sy))
|
||||
};
|
||||
// The vertex that ends this segment (a closing edge ends on the start).
|
||||
let end_vertex = if closes_onto_start(&segs, si) {
|
||||
let start = segs.get(..si).and_then(|s| s.iter().rposition(|x| matches!(x, Seg::M(_))));
|
||||
start.and_then(|m| vs.iter().position(|v| *v == m))
|
||||
} else {
|
||||
vs.iter().position(|v| *v == si)
|
||||
};
|
||||
let Some(ev) = end_vertex else { continue };
|
||||
let mut near: Option<(f64, f64)> = None;
|
||||
for k in 1..32 {
|
||||
let t = k as f64 / 32.0;
|
||||
if let Some(q) = at(t) {
|
||||
let d = (q - local).hypot();
|
||||
if d <= tol && near.is_none_or(|n| d < n.0) {
|
||||
near = Some((d, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some((mut d, mut t)) = near else { continue };
|
||||
// Narrow `t` down to the nearest place on the segment, where Add Point puts the point.
|
||||
let mut step = 1.0 / 32.0;
|
||||
for _ in 0..4 {
|
||||
step /= 8.0;
|
||||
let c = t;
|
||||
for k in -8..=8 {
|
||||
let u = (c + f64::from(k) * step).clamp(0.0, 1.0);
|
||||
if let Some(q) = at(u) {
|
||||
let e = (q - local).hypot();
|
||||
if e < d {
|
||||
(d, t) = (e, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(ev) = segment_end_vertex(&segs, &vs, si) else { continue };
|
||||
let Some((d, t)) = nearest_on_segment(&segs, si, p0, scale, local, tol) else { continue };
|
||||
if best.is_none_or(|b| d < b.0) {
|
||||
best = Some((d, pi, ev, t));
|
||||
}
|
||||
|
||||
+26
-37
@@ -301,6 +301,25 @@ impl Dpb {
|
||||
Ok(lists)
|
||||
}
|
||||
|
||||
/// Marks as unused every entry in state `mark` that `hit` selects.
|
||||
fn unmark_where(&mut self, mark: RefMark, hit: impl Fn(&DpbEntry) -> bool) {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == mark && hit(e) {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes every short-term entry that `hit` selects a long-term one with index `idx`.
|
||||
fn mark_long_where(&mut self, hit: impl Fn(&DpbEntry) -> bool, idx: u32) {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && hit(e) {
|
||||
e.mark = RefMark::Long;
|
||||
e.long_term_frame_idx = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One memory management control operation; returns true for MMCO 5.
|
||||
fn apply_mmco(&mut self, m: &Mmco, cur_frame_num: u32, max_frame_num: u32, cur_long: &mut Option<u32>) -> bool {
|
||||
let mut had5 = false;
|
||||
@@ -308,40 +327,17 @@ impl Dpb {
|
||||
match m.op {
|
||||
1 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_pic_num {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
self.unmark_where(RefMark::Short, |e| Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn);
|
||||
}
|
||||
2 => self.unmark_where(RefMark::Long, |e| e.long_term_frame_idx == m.long_term_pic_num),
|
||||
3 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_frame_idx {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Long;
|
||||
e.long_term_frame_idx = m.long_term_frame_idx;
|
||||
}
|
||||
}
|
||||
self.unmark_where(RefMark::Long, |e| e.long_term_frame_idx == m.long_term_frame_idx);
|
||||
self.mark_long_where(|e| Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn, m.long_term_frame_idx);
|
||||
}
|
||||
4 => {
|
||||
let max = m.max_long_term_frame_idx_plus1;
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && (max == 0 || e.long_term_frame_idx > max - 1) {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
self.unmark_where(RefMark::Long, |e| max == 0 || e.long_term_frame_idx > max - 1);
|
||||
self.max_long_term_frame_idx = if max == 0 { None } else { Some(max - 1) };
|
||||
}
|
||||
5 => {
|
||||
@@ -353,16 +349,9 @@ impl Dpb {
|
||||
}
|
||||
6 => {
|
||||
// Only an index within the allowed range may be assigned (8.2.5.4.6).
|
||||
let allowed = match self.max_long_term_frame_idx {
|
||||
Some(max) => m.long_term_frame_idx <= max,
|
||||
None => false,
|
||||
};
|
||||
let allowed = self.max_long_term_frame_idx.is_some_and(|max| m.long_term_frame_idx <= max);
|
||||
if allowed {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_frame_idx {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
self.unmark_where(RefMark::Long, |e| e.long_term_frame_idx == m.long_term_frame_idx);
|
||||
*cur_long = Some(m.long_term_frame_idx);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-45
@@ -261,68 +261,59 @@ impl<'a> SliceDecoder<'a> {
|
||||
if cur.long_term || col_diff == cur_diff { Some(mv) } else { Some(scale_mv(mv, col_diff, cur_diff)) }
|
||||
}
|
||||
|
||||
/// AMVP neighbour motion with the target's reference picture (no scaling), 8.5.3.2.7.
|
||||
fn amvp_exact(&self, f: &MvField, l: usize, tpoc: i32) -> Option<[i16; 2]> {
|
||||
let y = 1 - l;
|
||||
if f.pred(l) && self.ref_pic(l, f.ref_idx[l]).poc == tpoc {
|
||||
Some(f.mv[l])
|
||||
} else if f.pred(y) && self.ref_pic(y, f.ref_idx[y]).poc == tpoc {
|
||||
Some(f.mv[y])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// AMVP neighbour motion with a long-term-compatible reference, scaled when both are short-term.
|
||||
fn amvp_scaled(&self, f: &MvField, l: usize, tpoc: i32, tlt: bool, cur_poc: i32) -> Option<[i16; 2]> {
|
||||
let y = 1 - l;
|
||||
let pick = if f.pred(l) && self.ref_pic(l, f.ref_idx[l]).long_term == tlt {
|
||||
Some((f.mv[l], self.ref_pic(l, f.ref_idx[l])))
|
||||
} else if f.pred(y) && self.ref_pic(y, f.ref_idx[y]).long_term == tlt {
|
||||
Some((f.mv[y], self.ref_pic(y, f.ref_idx[y])))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
pick.map(|(mv, r)| if r.poc != tpoc && !r.long_term && !tlt { scale_mv(mv, cur_poc - r.poc, cur_poc - tpoc) } else { mv })
|
||||
}
|
||||
|
||||
/// The first neighbour whose position is available and whose motion `pick` accepts.
|
||||
fn first_amvp_neighbour(&self, pos: &[(i32, i32)], avail: &[bool], pick: impl Fn(&MvField) -> Option<[i16; 2]>) -> Option<[i16; 2]> {
|
||||
pos.iter().zip(avail).find_map(|(&(x, y), &ok)| if ok { pick(&self.mv_at(x, y)) } else { None })
|
||||
}
|
||||
|
||||
/// AMVP predictor (8.5.3.2.6, 8.5.3.2.7).
|
||||
pub fn derive_amvp(&self, g: PbGeom, l: usize, ref_idx: usize, mvp_flag: usize) -> [i16; 2] {
|
||||
let PbGeom { xp, yp, w, h, .. } = g;
|
||||
let target = &self.refs[l][ref_idx];
|
||||
let (tpoc, tlt) = (target.poc, target.long_term);
|
||||
let cur_poc = self.pic.poc;
|
||||
let y = 1 - l;
|
||||
// exact reference match (no scaling)
|
||||
let unscaled = |f: &MvField| -> Option<[i16; 2]> {
|
||||
if f.pred(l) && self.ref_pic(l, f.ref_idx[l]).poc == tpoc {
|
||||
Some(f.mv[l])
|
||||
} else if f.pred(y) && self.ref_pic(y, f.ref_idx[y]).poc == tpoc {
|
||||
Some(f.mv[y])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
// long-term-compatible match, scaled when both references are short-term
|
||||
let scaled = |f: &MvField| -> Option<[i16; 2]> {
|
||||
let pick = if f.pred(l) && self.ref_pic(l, f.ref_idx[l]).long_term == tlt {
|
||||
Some((f.mv[l], self.ref_pic(l, f.ref_idx[l])))
|
||||
} else if f.pred(y) && self.ref_pic(y, f.ref_idx[y]).long_term == tlt {
|
||||
Some((f.mv[y], self.ref_pic(y, f.ref_idx[y])))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
pick.map(|(mv, r)| if r.poc != tpoc && !r.long_term && !tlt { scale_mv(mv, cur_poc - r.poc, cur_poc - tpoc) } else { mv })
|
||||
};
|
||||
let exact = |f: &MvField| self.amvp_exact(f, l, tpoc);
|
||||
let scaled = |f: &MvField| self.amvp_scaled(f, l, tpoc, tlt, cur_poc);
|
||||
let pa = [(xp - 1, yp + h), (xp - 1, yp + h - 1)];
|
||||
let av_a = [self.pb_avail(&g, pa[0].0, pa[0].1), self.pb_avail(&g, pa[1].0, pa[1].1)];
|
||||
let is_scaled = av_a[0] || av_a[1];
|
||||
let mut a = None;
|
||||
for k in 0..2 {
|
||||
if av_a[k] && a.is_none() {
|
||||
a = unscaled(&self.mv_at(pa[k].0, pa[k].1));
|
||||
}
|
||||
}
|
||||
let mut a = self.first_amvp_neighbour(&pa, &av_a, &exact);
|
||||
if a.is_none() {
|
||||
for k in 0..2 {
|
||||
if av_a[k] && a.is_none() {
|
||||
a = scaled(&self.mv_at(pa[k].0, pa[k].1));
|
||||
}
|
||||
}
|
||||
a = self.first_amvp_neighbour(&pa, &av_a, &scaled);
|
||||
}
|
||||
let pb = [(xp + w, yp - 1), (xp + w - 1, yp - 1), (xp - 1, yp - 1)];
|
||||
let av_b = [0, 1, 2].map(|k| self.pb_avail(&g, pb[k].0, pb[k].1));
|
||||
let mut b = None;
|
||||
for k in 0..3 {
|
||||
if av_b[k] && b.is_none() {
|
||||
b = unscaled(&self.mv_at(pb[k].0, pb[k].1));
|
||||
}
|
||||
}
|
||||
let mut b = self.first_amvp_neighbour(&pb, &av_b, &exact);
|
||||
if !is_scaled && b.is_some() {
|
||||
a = b;
|
||||
}
|
||||
if !is_scaled {
|
||||
b = None;
|
||||
for k in 0..3 {
|
||||
if av_b[k] && b.is_none() {
|
||||
b = scaled(&self.mv_at(pb[k].0, pb[k].1));
|
||||
}
|
||||
}
|
||||
b = self.first_amvp_neighbour(&pb, &av_b, &scaled);
|
||||
}
|
||||
let mut list: [[i16; 2]; 3] = [[0; 2]; 3];
|
||||
let mut n = 0;
|
||||
|
||||
+57
-42
@@ -608,64 +608,73 @@ impl Chart {
|
||||
if ser.is_empty() {
|
||||
return (!secondary && self.kind != ChartType::Combo).then_some((0.0, 0.0));
|
||||
}
|
||||
let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
|
||||
let mut ext = (f64::INFINITY, f64::NEG_INFINITY);
|
||||
let has_stacked_or_percent = ser.iter().any(|s| {
|
||||
let k = self.series_kind(s);
|
||||
k.stacked() || k.percent()
|
||||
});
|
||||
|
||||
let mut percent = false;
|
||||
if has_stacked_or_percent {
|
||||
let n = ser.iter().map(|s| s.values.len()).max().unwrap_or(0);
|
||||
let budget = (ser.len() * n).min(10_000_000);
|
||||
let n_clamped = if ser.is_empty() { 0 } else { n.min(budget / ser.len()) };
|
||||
|
||||
for i in 0..n_clamped {
|
||||
let (mut p, mut m, mut stacked) = (0.0, 0.0, false);
|
||||
for s in &ser {
|
||||
let k = self.series_kind(s);
|
||||
let Some(v) = s.values.get(i).copied().flatten().filter(|v| v.is_finite()) else { continue };
|
||||
if k.percent() {
|
||||
percent = true;
|
||||
} else if k.stacked() {
|
||||
stacked = true;
|
||||
if v >= 0.0 { p += v } else { m += v }
|
||||
} else {
|
||||
hi = hi.max(v);
|
||||
lo = lo.min(v);
|
||||
}
|
||||
}
|
||||
if stacked {
|
||||
hi = hi.max(p);
|
||||
lo = lo.min(m);
|
||||
}
|
||||
}
|
||||
let percent = if has_stacked_or_percent {
|
||||
self.fold_stacked(&ser, &mut ext)
|
||||
} else {
|
||||
for s in &ser {
|
||||
for v in s.values.iter().flatten().copied().filter(|v| v.is_finite()) {
|
||||
hi = hi.max(v);
|
||||
lo = lo.min(v);
|
||||
widen(&mut ext, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
if !percent {
|
||||
for s in &ser {
|
||||
if self.series_kind(s).stacked() {
|
||||
continue;
|
||||
}
|
||||
for y in s.trendline_ys(crate::trendline::series_is_scatter(self.series_kind(s))) {
|
||||
hi = hi.max(y);
|
||||
lo = lo.min(y);
|
||||
}
|
||||
}
|
||||
self.fold_trendlines(&ser, &mut ext);
|
||||
}
|
||||
if percent {
|
||||
lo = lo.min(0.0);
|
||||
hi = hi.max(1.0);
|
||||
ext.0 = ext.0.min(0.0);
|
||||
ext.1 = ext.1.max(1.0);
|
||||
}
|
||||
let (lo, hi) = ext;
|
||||
Some(if lo <= hi { (lo, hi) } else { (0.0, 0.0) })
|
||||
}
|
||||
|
||||
/// Widens `ext` by the points of the stacked and 100 % series in `ser`; returns whether a
|
||||
/// 100 % series is among them.
|
||||
fn fold_stacked(&self, ser: &[&Series], ext: &mut (f64, f64)) -> bool {
|
||||
let mut percent = false;
|
||||
let n = ser.iter().map(|s| s.values.len()).max().unwrap_or(0);
|
||||
let budget = (ser.len() * n).min(10_000_000);
|
||||
let n_clamped = if ser.is_empty() { 0 } else { n.min(budget / ser.len()) };
|
||||
for i in 0..n_clamped {
|
||||
let (mut p, mut m, mut stacked) = (0.0, 0.0, false);
|
||||
for s in ser {
|
||||
let k = self.series_kind(s);
|
||||
let Some(v) = s.values.get(i).copied().flatten().filter(|v| v.is_finite()) else { continue };
|
||||
if k.percent() {
|
||||
percent = true;
|
||||
} else if k.stacked() {
|
||||
stacked = true;
|
||||
if v >= 0.0 { p += v } else { m += v }
|
||||
} else {
|
||||
widen(ext, v);
|
||||
}
|
||||
}
|
||||
if stacked {
|
||||
ext.1 = ext.1.max(p);
|
||||
ext.0 = ext.0.min(m);
|
||||
}
|
||||
}
|
||||
percent
|
||||
}
|
||||
|
||||
/// Widens `ext` by the trendlines of the series in `ser` that are not stacked.
|
||||
fn fold_trendlines(&self, ser: &[&Series], ext: &mut (f64, f64)) {
|
||||
for s in ser {
|
||||
if self.series_kind(s).stacked() {
|
||||
continue;
|
||||
}
|
||||
for y in s.trendline_ys(crate::trendline::series_is_scatter(self.series_kind(s))) {
|
||||
widen(ext, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A waterfall's bars as (from, to) on the value axis: each point floats from the running
|
||||
/// total, a total point stands on zero and sets it.
|
||||
pub fn waterfall_bars(&self) -> Vec<(f64, f64)> {
|
||||
@@ -1346,6 +1355,12 @@ impl Chart {
|
||||
}
|
||||
}
|
||||
|
||||
/// Widens the (min, max) extent `ext` to include `v`.
|
||||
fn widen(ext: &mut (f64, f64), v: f64) {
|
||||
ext.0 = ext.0.min(v);
|
||||
ext.1 = ext.1.max(v);
|
||||
}
|
||||
|
||||
/// Excel's QUARTILE.EXC position `p·(n+1)` (1-based) on sorted data, clamped to the ends.
|
||||
fn quantile_exc(v: &[f64], p: f64) -> f64 {
|
||||
let n = v.len();
|
||||
|
||||
+86
-65
@@ -147,6 +147,86 @@ pub fn look(ctx: &Ctx, t: &Table, r: usize, c: usize) -> CellLook {
|
||||
look_with_def(ctx, resolve_style(ctx, t), t, r, c)
|
||||
}
|
||||
|
||||
/// Which parts of the table a cell sits in, for the preset styles.
|
||||
#[derive(Clone, Copy)]
|
||||
struct PresetPart {
|
||||
header: bool,
|
||||
total: bool,
|
||||
first_col: bool,
|
||||
last_col: bool,
|
||||
banded: bool,
|
||||
}
|
||||
|
||||
impl PresetPart {
|
||||
/// In a header, total, first or last column: the parts a preset style makes strong.
|
||||
fn strong(self) -> bool {
|
||||
self.header || self.total || self.first_col || self.last_col
|
||||
}
|
||||
}
|
||||
|
||||
/// Tint `k` of accent `a`.
|
||||
fn tint(a: Rgba, k: i32) -> Rgba {
|
||||
deckcraft_color::apply(a, &[ColorTransform::Tint(k)])
|
||||
}
|
||||
|
||||
fn light1_look(a: Rgba, p: PresetPart, on: impl Fn(Rgba) -> Rgba) -> CellLook {
|
||||
let fill = p.banded.then(|| tint(a, 20000));
|
||||
CellLook { fill, text: fill.map(on), bold: p.strong(), border: if p.header { Some((a, 1.0)) } else { None }, sides: [None; 4], paint: None }
|
||||
}
|
||||
|
||||
fn light2_look(a: Rgba, p: PresetPart) -> CellLook {
|
||||
CellLook {
|
||||
fill: if p.header { Some(a) } else { None },
|
||||
text: if p.header { Some(Rgba::WHITE) } else { None },
|
||||
bold: p.header || p.total || p.first_col,
|
||||
border: Some((a, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn dark1_look(a: Rgba, p: PresetPart) -> CellLook {
|
||||
CellLook {
|
||||
fill: Some(if p.header {
|
||||
Rgba::BLACK
|
||||
} else if p.banded {
|
||||
deckcraft_color::apply(a, &[ColorTransform::Shade(60000)])
|
||||
} else {
|
||||
deckcraft_color::apply(a, &[ColorTransform::Shade(75000)])
|
||||
}),
|
||||
text: Some(Rgba::WHITE),
|
||||
bold: p.header || p.total || p.first_col,
|
||||
border: Some((Rgba::WHITE, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn medium4_look(a: Rgba, p: PresetPart, on: impl Fn(Rgba) -> Rgba) -> CellLook {
|
||||
let fill = if p.header || p.banded { tint(a, 40000) } else { tint(a, 20000) };
|
||||
CellLook { fill: Some(fill), text: Some(on(fill)), bold: p.strong(), border: Some((a, 0.75)), sides: [None; 4], paint: None }
|
||||
}
|
||||
|
||||
/// medium2: strong header, banded light tints, white grid.
|
||||
fn medium2_look(a: Rgba, p: PresetPart, on: impl Fn(Rgba) -> Rgba) -> CellLook {
|
||||
let strong = p.strong();
|
||||
let fill = if strong {
|
||||
a
|
||||
} else if p.banded {
|
||||
tint(a, 40000)
|
||||
} else {
|
||||
tint(a, 20000)
|
||||
};
|
||||
CellLook {
|
||||
fill: Some(fill),
|
||||
text: Some(if strong { Rgba::WHITE } else { on(fill) }),
|
||||
bold: strong,
|
||||
border: Some((Rgba::WHITE, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn look_with_def(ctx: &Ctx, def: Option<&TableStyleDef>, t: &Table, r: usize, c: usize) -> CellLook {
|
||||
if let Some(def) = def {
|
||||
return look_def(ctx, def, t, r, c);
|
||||
@@ -160,8 +240,7 @@ pub fn look_with_def(ctx: &Ctx, def: Option<&TableStyleDef>, t: &Table, r: usize
|
||||
let body_r = if t.first_row { r.wrapping_sub(1) } else { r };
|
||||
let band = t.band_row && !header && !total && body_r % 2 == 0;
|
||||
let band_c = t.band_col && c.is_multiple_of(2);
|
||||
let tint = |k: i32| deckcraft_color::apply(a, &[ColorTransform::Tint(k)]);
|
||||
let white = Rgba::WHITE;
|
||||
let p = PresetPart { header, total, first_col, last_col, banded: band || band_c };
|
||||
let text_color = ctx.color(&ColorRef::scheme(SchemeSlot::Tx1), None);
|
||||
// Text on a tinted cell never inherits the text colour, which a dark theme makes light and
|
||||
// unreadable on the light tint: PowerPoint's medium styles set it to the theme's dark colour
|
||||
@@ -173,69 +252,11 @@ pub fn look_with_def(ctx: &Ctx, def: Option<&TableStyleDef>, t: &Table, r: usize
|
||||
match kind {
|
||||
"none" => CellLook { fill: None, text: None, bold: false, border: None, sides: [None; 4], paint: None },
|
||||
"grid" => CellLook { fill: None, text: None, bold: false, border: Some((text_color, 0.75)), sides: [None; 4], paint: None },
|
||||
"light1" => {
|
||||
let fill = (band || band_c).then(|| tint(20000));
|
||||
CellLook {
|
||||
fill,
|
||||
text: fill.map(on),
|
||||
bold: header || total || first_col || last_col,
|
||||
border: if header { Some((a, 1.0)) } else { None },
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
"light2" => CellLook {
|
||||
fill: if header { Some(a) } else { None },
|
||||
text: if header { Some(white) } else { None },
|
||||
bold: header || total || first_col,
|
||||
border: Some((a, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
},
|
||||
"dark1" => CellLook {
|
||||
fill: Some(if header {
|
||||
Rgba::BLACK
|
||||
} else if band || band_c {
|
||||
deckcraft_color::apply(a, &[ColorTransform::Shade(60000)])
|
||||
} else {
|
||||
deckcraft_color::apply(a, &[ColorTransform::Shade(75000)])
|
||||
}),
|
||||
text: Some(white),
|
||||
bold: header || total || first_col,
|
||||
border: Some((white, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
},
|
||||
"medium4" => {
|
||||
let fill = if header || band || band_c { tint(40000) } else { tint(20000) };
|
||||
CellLook {
|
||||
fill: Some(fill),
|
||||
text: Some(on(fill)),
|
||||
bold: header || total || first_col || last_col,
|
||||
border: Some((a, 0.75)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// medium2: strong header, banded light tints, white grid.
|
||||
let strong = header || total || first_col || last_col;
|
||||
let fill = if strong {
|
||||
a
|
||||
} else if band || band_c {
|
||||
tint(40000)
|
||||
} else {
|
||||
tint(20000)
|
||||
};
|
||||
CellLook {
|
||||
fill: Some(fill),
|
||||
text: Some(if strong { white } else { on(fill) }),
|
||||
bold: strong,
|
||||
border: Some((white, 1.0)),
|
||||
sides: [None; 4],
|
||||
paint: None,
|
||||
}
|
||||
}
|
||||
"light1" => light1_look(a, p, on),
|
||||
"light2" => light2_look(a, p),
|
||||
"dark1" => dark1_look(a, p),
|
||||
"medium4" => medium4_look(a, p, on),
|
||||
_ => medium2_look(a, p, on),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user