use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
use cassowary::strength::WEAK;
use cassowary::WeightedRelation::{EQ, GE, LE};
use cassowary::{strength::REQUIRED, Expression, Solver, Term, Variable};
use piet::kurbo::{Affine, Rect};
use piet::RenderContext;
use crate::theme::MAKIE;
use crate::widget_size::WidgetSizeVars;
use crate::{Renderable, Widget};
pub struct Rectangle {
pub start_col: usize,
pub start_row: usize,
pub end_col: usize,
pub end_row: usize,
}
struct Row {
top: Variable,
main: Variable,
bottom: Variable,
}
impl Row {
fn new() -> Self {
Self {
top: Variable::new(),
main: Variable::new(),
bottom: Variable::new(),
}
}
}
struct Col {
left: Variable,
main: Variable,
right: Variable,
}
impl Col {
fn new() -> Self {
Self {
left: Variable::new(),
main: Variable::new(),
right: Variable::new(),
}
}
}
pub struct Grid {
pub items: Vec<GridItem>,
pub size: WidgetSizeVars,
rows: Vec<Row>,
cols: Vec<Col>,
}
pub enum Side {
TopLeft,
Top,
TopRight,
Left,
Right,
BottomLeft,
Bottom,
BottomRight,
}
pub trait AsRange {
fn max(&self) -> Option<usize>;
fn as_range(&self, n: usize) -> Range<usize>;
}
impl AsRange for usize {
fn max(&self) -> Option<usize> {
Some(*self)
}
fn as_range(&self, _: usize) -> Range<usize> {
*self..*self + 1
}
}
impl AsRange for Range<usize> {
fn max(&self) -> Option<usize> {
if self.end > 0 {
Some(self.end - 1)
} else {
None
}
}
fn as_range(&self, _: usize) -> Range<usize> {
self.clone()
}
} impl AsRange for RangeFrom<usize> {
fn max(&self) -> Option<usize> {
None
}
fn as_range(&self, n: usize) -> Range<usize> {
self.start..n
}
} impl AsRange for RangeFull {
fn max(&self) -> Option<usize> {
None
}
fn as_range(&self, n: usize) -> Range<usize> {
0..n
}
} impl AsRange for RangeInclusive<usize> {
fn max(&self) -> Option<usize> {
Some(*self.end())
}
fn as_range(&self, _: usize) -> Range<usize> {
*self.start()..*self.end() + 1
}
} impl AsRange for RangeTo<usize> {
fn max(&self) -> Option<usize> {
if self.end > 0 {
Some(self.end - 1)
} else {
None
}
}
fn as_range(&self, _: usize) -> Range<usize> {
0..self.end
}
} impl AsRange for RangeToInclusive<usize> {
fn max(&self) -> Option<usize> {
Some(self.end)
}
fn as_range(&self, _: usize) -> Range<usize> {
0..self.end + 1
}
}
pub struct GridItem {
col_range: Box<dyn AsRange>,
row_range: Box<dyn AsRange>,
side: Option<Side>,
widget: Widget,
}
impl Grid {
fn main_height_expr(&self, range: std::ops::Range<usize>) -> Expression {
let mut terms: Vec<Term> = vec![];
for i in range.clone() {
if i != range.start {
terms.push(self.rows[i].top * 1.0);
}
terms.push(self.rows[i].main * 1.0_f64);
if i + 1 != range.end {
terms.push(self.rows[i].bottom * 1.0);
}
}
let num_rows = range.end - range.start;
Expression::new(terms, (num_rows - 1) as f64 * MAKIE.grid.row_padding)
}
fn main_width_expr(&self, range: std::ops::Range<usize>) -> Expression {
let mut terms: Vec<Term> = vec![];
for i in range.clone() {
if i != range.start {
terms.push(self.cols[i].left * 1.0);
}
terms.push(self.cols[i].main * 1.0);
if i + 1 != range.end {
terms.push(self.cols[i].right * 1.0);
}
}
let num_cols = range.end - range.start;
Expression::new(terms, (num_cols - 1) as f64 * MAKIE.grid.col_padding)
}
}
impl Grid {
pub fn new() -> Self {
Self {
items: Default::default(),
size: WidgetSizeVars::new(),
rows: vec![],
cols: vec![],
}
}
pub fn push<X: AsRange + 'static, Y: AsRange + 'static, W: Into<Widget>>(
&mut self,
row_range: Y,
col_range: X,
widget: W,
) {
self.push_side(row_range, col_range, None, widget.into())
}
fn push_side<X: AsRange + 'static, Y: AsRange + 'static, W: Into<Widget>>(
&mut self,
row_range: Y,
col_range: X,
side: Option<Side>,
widget: W,
) {
if let Some(x) = row_range.max() {
if x + 1 > self.rows.len() {
self.rows.resize_with(x + 1, Row::new);
}
}
if let Some(x) = col_range.max() {
if x + 1 > self.cols.len() {
self.cols.resize_with(x + 1, Col::new);
}
}
self.items.push(GridItem {
col_range: Box::new(col_range),
row_range: Box::new(row_range),
side,
widget: widget.into(),
});
}
}
impl Renderable for Grid {
fn render<C: RenderContext>(&self, ctx: &mut C, layout: &Solver) {
let n_rows = self.rows.len();
let n_cols = self.cols.len();
let row_heights: Vec<f64> = {
let mut v = vec![];
for row in &self.rows {
v.push(layout.get_value(row.top));
v.push(layout.get_value(row.main));
v.push(layout.get_value(row.bottom));
}
v
};
let col_widths: Vec<f64> = {
let mut v = vec![];
for col in &self.cols {
v.push(layout.get_value(col.left));
v.push(layout.get_value(col.main));
v.push(layout.get_value(col.right));
}
v
};
for item in &self.items {
let row_range = item.row_range.as_range(n_rows);
let col_range = item.col_range.as_range(n_cols);
let main_y0: f64 = row_heights[1..row_range.start * 3 + 1].iter().sum::<f64>()
+ MAKIE.grid.row_padding * row_range.start as f64;
let main_x0: f64 = col_widths[1..col_range.start * 3 + 1].iter().sum::<f64>()
+ MAKIE.grid.col_padding * col_range.start as f64;
let top = row_heights[row_range.start * 3];
let left = col_widths[col_range.start * 3];
let bottom = row_heights[row_range.end * 3 - 1];
let right = col_widths[col_range.end * 3 - 1];
let main_width = layout.get_value(item.widget.size().main_width);
let main_height = layout.get_value(item.widget.size().main_height);
match &item.side {
None => {
ctx.with_save(|ctx: &mut C| {
ctx.transform(Affine::translate((main_x0, main_y0)));
ctx.clip(Rect {
x0: -left,
y0: -top,
x1: main_width + right,
y1: main_height + bottom,
});
item.widget.render(ctx, layout);
Ok(())
})
.unwrap();
}
Some(_side) => {
todo!()
}
}
}
}
fn layout<C: RenderContext>(&self, ctx: &mut C, solver: &mut cassowary::Solver) {
let n_rows = self.rows.len();
let n_cols = self.cols.len();
solver
.add_constraints(&[
self.size.top | EQ(REQUIRED) | self.rows[0].top,
self.size.bottom | EQ(REQUIRED) | self.rows[n_rows - 1].bottom,
self.size.left | EQ(REQUIRED) | self.cols[0].left,
self.size.right | EQ(REQUIRED) | self.cols[n_cols - 1].right,
])
.unwrap();
if !self.items.is_empty() {
solver
.add_constraints(&[
self.main_width_expr(0..self.cols.len()) | EQ(REQUIRED) | self.size.main_width,
self.main_height_expr(0..self.rows.len())
| EQ(REQUIRED)
| self.size.main_height, ])
.unwrap();
let default_row_height =
(self.size.height() - MAKIE.grid.row_padding * (n_rows - 1) as f64) / n_rows as f64;
let default_col_width =
(self.size.width() - MAKIE.grid.col_padding * (n_cols - 1) as f64) / n_cols as f64;
for row in &self.rows {
solver
.add_constraints(&[
(row.top + row.main + row.bottom) | EQ(WEAK) | default_row_height.clone(),
row.top | EQ(WEAK) | 0.0_f64,
row.bottom | EQ(WEAK) | 0.0_f64,
row.top | GE(REQUIRED) | 0.0_f64,
row.bottom | GE(REQUIRED) | 0.0_f64,
])
.unwrap();
}
for col in &self.cols {
solver
.add_constraints(&[
(col.left + col.main + col.right) | EQ(WEAK) | default_col_width.clone(),
col.left | EQ(WEAK) | 0.0_f64,
col.right | EQ(WEAK) | 0.0_f64,
col.left | GE(REQUIRED) | 0.0_f64,
col.right | GE(REQUIRED) | 0.0_f64,
])
.unwrap();
}
for item in &self.items {
let row_range = item.row_range.as_range(n_rows);
let col_range = item.col_range.as_range(n_cols);
let item_main_height = self.main_height_expr(row_range.clone());
let item_main_width = self.main_width_expr(col_range.clone());
solver
.add_constraints(&[
item_main_height | EQ(REQUIRED) | item.widget.size().main_height,
item_main_width | EQ(REQUIRED) | item.widget.size().main_width,
])
.unwrap();
solver
.add_constraints(&[
item.widget.size().top | LE(REQUIRED) | self.rows[row_range.start].top,
item.widget.size().bottom
| LE(REQUIRED)
| self.rows[row_range.end - 1].bottom,
item.widget.size().left | LE(REQUIRED) | self.cols[col_range.start].left,
item.widget.size().right
| LE(REQUIRED)
| self.cols[col_range.end - 1].right,
])
.unwrap();
item.widget.layout(ctx, solver);
}
}
}
fn size(&self) -> &crate::widget_size::WidgetSizeVars {
&self.size
}
}