use smallvec::SmallVec;
use std::iter::Sum;
use super::Stretch;
use crate::cast::{Cast, Conv};
#[allow(unused)] use super::FrameRules;
#[allow(unused)] use crate::theme::SizeCx;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct SizeRules {
a: i32,
b: i32,
m: (u16, u16),
stretch: Stretch,
}
impl SizeRules {
pub const EMPTY: Self = SizeRules::empty(Stretch::None);
#[inline]
pub const fn empty(stretch: Stretch) -> Self {
SizeRules {
a: 0,
b: 0,
m: (0, 0),
stretch,
}
}
#[inline]
pub fn fixed(size: i32) -> Self {
debug_assert!(size >= 0);
SizeRules {
a: size,
b: size,
m: (0, 0),
stretch: Stretch::None,
}
}
#[inline]
pub fn new(min: i32, ideal: i32, stretch: Stretch) -> Self {
debug_assert!(0 <= min && 0 <= ideal);
SizeRules {
a: min,
b: ideal.max(min),
m: (0, 0),
stretch,
}
}
#[inline]
pub fn with_margin(mut self, margin: u16) -> Self {
self.m = (margin, margin);
self
}
#[inline]
pub fn with_margins(mut self, (first, second): (u16, u16)) -> Self {
self.m = (first, second);
self
}
#[inline]
pub fn with_stretch(mut self, stretch: Stretch) -> Self {
self.stretch = stretch;
self
}
#[inline]
pub fn min_size(self) -> i32 {
self.a
}
#[inline]
pub fn ideal_size(self) -> i32 {
self.b
}
#[inline]
pub fn margins(self) -> (u16, u16) {
self.m
}
#[inline]
pub fn margins_i32(self) -> (i32, i32) {
(self.m.0.into(), self.m.1.into())
}
#[inline]
pub fn stretch(self) -> Stretch {
self.stretch
}
#[inline]
pub fn set_stretch(&mut self, stretch: Stretch) {
self.stretch = stretch;
}
#[inline]
pub fn set_margins(&mut self, margins: (u16, u16)) {
self.m = margins;
}
#[must_use = "method does not modify self but returns a new value"]
pub fn max(self, rhs: Self) -> SizeRules {
SizeRules {
a: self.a.max(rhs.a),
b: self.b.max(rhs.b),
m: (self.m.0.max(rhs.m.0), self.m.1.max(rhs.m.1)),
stretch: self.stretch.max(rhs.stretch),
}
}
#[inline]
pub fn max_with(&mut self, rhs: Self) {
*self = self.max(rhs);
}
pub fn multiply_with_margin(&mut self, min_factor: i32, ideal_factor: i32) {
let margin = i32::from(self.m.0).max(i32::from(self.m.1));
assert!(min_factor > 0);
assert!(ideal_factor > 0);
self.a = min_factor * self.a + (min_factor - 1) * margin;
self.b = ideal_factor * self.b + (ideal_factor - 1) * margin;
}
pub fn append(&mut self, rhs: SizeRules) {
let c: i32 = self.m.1.max(rhs.m.0).into();
self.a += rhs.a + c;
self.b += rhs.b + c;
self.m.1 = rhs.m.1;
self.stretch = self.stretch.max(rhs.stretch);
}
#[must_use = "method does not modify self but returns a new value"]
pub fn appended(self, rhs: SizeRules) -> Self {
let c: i32 = self.m.1.max(rhs.m.0).into();
SizeRules {
a: self.a + rhs.a + c,
b: self.b + rhs.b + c,
m: (self.m.0, rhs.m.1),
stretch: self.stretch.max(rhs.stretch),
}
}
pub fn sum(range: &[SizeRules]) -> SizeRules {
range.iter().sum()
}
pub fn min_sum(range: &[SizeRules]) -> SizeRules {
if range.is_empty() {
return SizeRules::EMPTY;
}
let mut rules = range[0];
for r in &range[1..] {
rules.a += i32::from(rules.m.1.max(r.m.0)) + r.a;
}
rules.b = rules.a;
rules.m.1 = range[range.len() - 1].m.1;
rules
}
pub fn sub_add(&mut self, x: Self, y: Self) {
self.a = (self.a - x.a + y.a).max(0);
self.b = (self.b - x.b + y.b).max(0);
self.m.1 = y.m.1;
self.stretch = self.stretch.max(y.stretch);
}
#[inline]
pub fn reduce_min_to(&mut self, min: i32) {
self.a = self.a.min(min);
}
#[inline]
pub fn solve_widths(widths: &mut [i32], rules: &[Self], target: i32) {
let total = SizeRules::sum(rules);
Self::solve_widths_with_total(widths, rules, total, target);
}
pub fn solve_widths_with_total(widths: &mut [i32], rules: &[Self], total: Self, target: i32) {
Self::solve_widths_impl(widths, rules, total, target, Even)
}
#[allow(clippy::collapsible_if, clippy::needless_return)]
pub fn solve_widths_with_priority(widths: &mut [i32], rules: &[Self], target: i32, last: bool) {
let total = SizeRules::sum(rules);
Self::solve_widths_impl(widths, rules, total, target, Prioritize { last })
}
#[allow(
clippy::comparison_chain,
clippy::needless_range_loop,
clippy::needless_return
)]
#[inline(always)]
fn solve_widths_impl(
out: &mut [i32],
rules: &[Self],
total: Self,
target: i32,
pri: impl SolvePriority,
) {
#[allow(non_snake_case)]
let N = out.len();
assert_eq!(rules.len(), N);
if N == 0 {
return;
}
#[cfg(debug_assertions)]
{
assert!(out.iter().all(|w| *w >= 0));
let mut sum = SizeRules::sum(rules);
sum.m = total.m; assert_eq!(sum, total);
}
if target <= total.a {
for n in 0..N {
out[n] = rules[n].a;
}
return;
}
let highest_stretch = total.stretch;
out[0] = out[0].max(rules[0].a);
let mut margin_sum = 0;
let mut sum = out[0];
let mut dist_under_b = (rules[0].b - out[0]).max(0);
let mut dist_over_b = (out[0] - rules[0].b).max(0);
let mut dist_over_b_lower_stretch = 0;
if rules[0].stretch < highest_stretch {
dist_over_b_lower_stretch = dist_over_b;
}
for i in 1..N {
out[i] = out[i].max(rules[i].a);
margin_sum += i32::from((rules[i - 1].m.1).max(rules[i].m.0));
sum += out[i];
dist_under_b += (rules[i].b - out[i]).max(0);
let over = (out[i] - rules[i].b).max(0);
dist_over_b += over;
if rules[i].stretch < highest_stretch {
dist_over_b_lower_stretch += over;
}
}
let target = target - margin_sum;
let mut to_shrink = sum + dist_under_b - target;
if dist_over_b <= to_shrink {
let mut targets = Targets::new();
sum = 0;
for i in 0..N {
out[i] = out[i].min(rules[i].b);
sum += out[i];
if out[i] > rules[i].a {
targets.push(i.cast());
}
}
if sum > target {
let avail = target + margin_sum - total.a;
reduce_targets(out, &mut targets, |i| rules[i].a, avail);
debug_assert_eq!(target, (0..N).fold(0, |x, i| x + out[i]));
return;
}
} else if dist_over_b_lower_stretch >= to_shrink {
sum = 0;
for i in 0..N {
if rules[i].stretch < highest_stretch {
out[i] = out[i].min(rules[i].b);
}
sum += out[i];
}
} else if to_shrink > 0 {
if let Some(prioritize_last) = pri.priority() {
if !prioritize_last {
for i in 0..N {
if out[i] > rules[i].b {
let decr = to_shrink.min((out[i] - rules[i].b).max(0));
to_shrink -= decr;
out[i] -= decr;
}
}
} else {
for i in (0..N).rev() {
if out[i] > rules[i].b {
let decr = to_shrink.min((out[i] - rules[i].b).max(0));
to_shrink -= decr;
out[i] -= decr;
}
}
}
debug_assert_eq!(to_shrink, 0);
} else {
const MAX_STRETCH: usize = Stretch::Maximize as usize + 1;
let mut dists = [0; MAX_STRETCH];
for i in 0..N {
dists[rules[i].stretch as usize] += (out[i] - rules[i].b).max(0);
}
let mut accum = 0;
let mut highest_affected = 0;
for i in 0..MAX_STRETCH {
highest_affected = i;
dists[i] += accum;
accum = dists[i];
if accum >= to_shrink {
break;
}
}
sum = 0;
let mut avail = 0;
let mut targets = Targets::new();
for i in 0..N {
let stretch = rules[i].stretch as usize;
if out[i] > rules[i].b {
let over = (out[i] - rules[i].b).max(0);
if stretch < highest_affected {
out[i] = rules[i].b;
} else if stretch == highest_affected {
avail += over;
targets.push(i.cast());
}
}
sum += out[i];
}
let to_shrink = sum + dist_under_b - target;
if 0 < to_shrink && to_shrink <= avail {
avail -= to_shrink;
reduce_targets(out, &mut targets, |i| rules[i].b, avail);
sum -= to_shrink;
}
}
}
if sum < target {
if target - sum >= dist_under_b {
sum = 0;
if let Some(prioritize_last) = pri.priority() {
let mut pick = None;
for i in 0..N {
out[i] = out[i].max(rules[i].b);
sum += out[i];
if highest_stretch > Stretch::None && rules[i].stretch == highest_stretch {
if prioritize_last || pick.is_none() {
pick = Some(i);
}
}
}
if let Some(i) = pick {
out[i] += target - sum;
}
} else {
let mut targets = Targets::new();
let mut over = 0;
for i in 0..N {
out[i] = out[i].max(rules[i].b);
sum += out[i];
if highest_stretch > Stretch::None && rules[i].stretch == highest_stretch {
over += out[i] - rules[i].b;
targets.push(i.cast());
}
}
let avail = target - sum + over;
increase_targets(out, &mut targets, |i| rules[i].b, avail);
}
debug_assert!(target >= (0..N).fold(0, |x, i| x + out[i]));
return;
} else {
let mut targets = Targets::new();
let mut over = 0;
for i in 0..N {
if out[i] < rules[i].b {
over += out[i] - rules[i].a;
targets.push(i.cast());
}
}
let avail = target - sum + over;
increase_targets(out, &mut targets, |i| rules[i].a, avail);
}
}
debug_assert_eq!(
target,
(0..N).fold(0, |x, i| x + out[i]),
"widths {out:?} not equal to target {target}"
);
}
pub(crate) fn distribute_stretch_over_by(self, rules: &mut [Self], scores: &[u32]) {
assert_eq!(rules.len(), scores.len());
if rules.iter().any(|r| r.stretch >= self.stretch) {
return;
}
let highest = scores.iter().cloned().max().unwrap_or(0);
for i in 0..rules.len() {
if scores[i] == highest {
rules[i].stretch = self.stretch;
}
}
}
pub(crate) fn distribute_span_over(self, rules: &mut [Self]) {
let len = rules.len();
assert!(len > 0);
let len1 = len - 1;
let sum: SizeRules = rules.iter().sum();
rules[0].m.0 = rules[0].m.0.max(self.m.0);
rules[len1].m.1 = rules[len1].m.1.max(self.m.1);
let excess_a = (self.a - sum.a).max(0);
let excess_b = (self.b - sum.b).max(0);
if excess_a == 0 && excess_b == 0 {
return;
}
let highest_stretch = sum.stretch;
let count = i32::conv(
(0..len)
.filter(|i| rules[*i].stretch == highest_stretch)
.count(),
);
let a_per_elt = excess_a / count;
let b_per_elt = excess_b / count;
let mut extra_a = excess_a - count * a_per_elt;
let mut extra_b = excess_b - count * b_per_elt;
for rules in rules.iter_mut() {
if rules.stretch == highest_stretch {
rules.a += a_per_elt;
rules.b += b_per_elt;
if extra_a > 0 {
rules.a += 1;
extra_a -= 1;
}
if extra_b > 0 {
rules.b += 1;
extra_b -= 1;
}
if highest_stretch < self.stretch {
rules.stretch = self.stretch;
}
}
}
}
}
trait SolvePriority: std::fmt::Debug {
fn priority(&self) -> Option<bool>;
}
#[derive(Debug)]
struct Even;
impl SolvePriority for Even {
#[inline]
fn priority(&self) -> Option<bool> {
None
}
}
#[derive(Debug)]
struct Prioritize {
last: bool,
}
impl SolvePriority for Prioritize {
#[inline]
fn priority(&self) -> Option<bool> {
Some(self.last)
}
}
impl Sum for SizeRules {
fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
if let Some(first) = iter.next() {
iter.fold(first, |x, y| x.appended(y))
} else {
SizeRules::EMPTY
}
}
}
impl<'a> Sum<&'a Self> for SizeRules {
fn sum<I: Iterator<Item = &'a Self>>(mut iter: I) -> Self {
if let Some(first) = iter.next() {
iter.fold(*first, |x, y| x.appended(*y))
} else {
SizeRules::EMPTY
}
}
}
type Targets = SmallVec<[i32; 16]>;
fn increase_targets(
out: &mut [i32],
targets: &mut Targets,
base: impl Fn(usize) -> i32,
mut avail: i32,
) {
if targets.is_empty() {
return;
}
let mut any_removed = true;
while any_removed {
any_removed = false;
let count = i32::conv(targets.len());
let ceil = (avail + count - 1) / count; let mut t = 0;
while t < targets.len() {
let i = usize::conv(targets[t]);
if out[i] >= base(i) + ceil {
avail -= out[i] - base(i);
targets.remove(t);
any_removed = true;
continue;
}
t += 1;
}
if targets.is_empty() {
return;
}
}
let count = i32::conv(targets.len());
let per_elt = avail / count;
let extra = usize::conv(avail - per_elt * count);
assert!(extra < targets.len());
for t in 0..extra {
let i = usize::conv(targets[t]);
out[i] = base(i) + per_elt + 1;
}
for t in extra..targets.len() {
let i = usize::conv(targets[t]);
out[i] = base(i) + per_elt;
}
}
fn reduce_targets<F: Fn(usize) -> i32>(
out: &mut [i32],
targets: &mut Targets,
base: F,
mut avail: i32,
) {
debug_assert!(avail >= 0);
let mut any_removed = true;
while any_removed {
any_removed = false;
let floor = avail / i32::conv(targets.len());
let mut t = 0;
while t < targets.len() {
let i = usize::conv(targets[t]);
let base = base(i);
debug_assert!(out[i] >= base);
if out[i] <= base + floor {
avail -= out[i] - base;
targets.remove(t);
any_removed = true;
continue;
}
t += 1;
}
}
let floor = avail / i32::conv(targets.len());
let extra = usize::conv(avail) - usize::conv(floor) * targets.len();
assert!(extra < targets.len());
for t in 0..extra {
let i = usize::conv(targets[t]);
out[i] = base(i) + floor + 1;
}
for t in extra..targets.len() {
let i = usize::conv(targets[t]);
out[i] = base(i) + floor;
}
}