use iced::{
Element, Length,
border::Radius,
widget::{column, container, opaque, row, space},
};
use snora_core::{LayoutDirection, Sheet, SheetEdge, SheetSize};
pub(crate) fn render_sheet<'a, Message>(
sheet: Sheet<Element<'a, Message>, Message>,
direction: LayoutDirection,
) -> Element<'a, Message>
where
Message: Clone + 'a,
{
let edge = sheet.edge;
let size = sheet.size;
let body_surface = container(sheet.content)
.style(move |theme: &iced::Theme| sheet_surface_style(theme, edge, direction))
.width(Length::Fill)
.height(Length::Fill);
let body = opaque(body_surface);
if edge.is_vertical() {
render_vertical(body, edge, size)
} else {
render_horizontal(body, edge, size, direction)
}
}
fn render_vertical<'a, Message>(
body: Element<'a, Message>,
edge: SheetEdge,
size: SheetSize,
) -> Element<'a, Message>
where
Message: Clone + 'a,
{
let (top_cell, bottom_cell) = match (edge, size) {
(SheetEdge::Bottom, SheetSize::Pixels(px)) => (
Element::from(space().height(Length::Fill)),
Element::from(container(body).width(Length::Fill).height(Length::Fixed(px))),
),
(SheetEdge::Top, SheetSize::Pixels(px)) => (
Element::from(container(body).width(Length::Fill).height(Length::Fixed(px))),
Element::from(space().height(Length::Fill)),
),
(SheetEdge::Bottom, _) => {
let ratio = size.as_ratio().unwrap_or(1.0 / 3.0);
let (spacer_pct, sheet_pct) = portions(ratio);
(
Element::from(space().height(Length::FillPortion(spacer_pct))),
Element::from(
container(body)
.width(Length::Fill)
.height(Length::FillPortion(sheet_pct)),
),
)
}
(SheetEdge::Top, _) => {
let ratio = size.as_ratio().unwrap_or(1.0 / 3.0);
let (sheet_pct, spacer_pct) = (
(ratio * 100.0).round().clamp(1.0, 100.0) as u16,
{
let s = (ratio * 100.0).round().clamp(0.0, 100.0) as u16;
100u16.saturating_sub(s).max(1)
},
);
(
Element::from(
container(body)
.width(Length::Fill)
.height(Length::FillPortion(sheet_pct)),
),
Element::from(space().height(Length::FillPortion(spacer_pct))),
)
}
_ => unreachable!("render_vertical called with horizontal edge"),
};
column![top_cell, bottom_cell]
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn render_horizontal<'a, Message>(
body: Element<'a, Message>,
edge: SheetEdge,
size: SheetSize,
direction: LayoutDirection,
) -> Element<'a, Message>
where
Message: Clone + 'a,
{
let on_left = match (edge, direction) {
(SheetEdge::Start, LayoutDirection::Ltr) => true,
(SheetEdge::Start, LayoutDirection::Rtl) => false,
(SheetEdge::End, LayoutDirection::Ltr) => false,
(SheetEdge::End, LayoutDirection::Rtl) => true,
_ => unreachable!("render_horizontal called with vertical edge"),
};
let (left_cell, right_cell) = match size {
SheetSize::Pixels(px) => {
let sheet_cell = container(body).width(Length::Fixed(px)).height(Length::Fill);
let spacer_cell = space().width(Length::Fill);
if on_left {
(
Element::from(sheet_cell),
Element::from(spacer_cell),
)
} else {
(
Element::from(spacer_cell),
Element::from(sheet_cell),
)
}
}
_ => {
let ratio = size.as_ratio().unwrap_or(1.0 / 3.0);
let (sheet_pct, spacer_pct) = if on_left {
let (a, b) = portions(ratio);
(b, a)
} else {
portions(ratio)
};
if on_left {
(
Element::from(
container(body)
.width(Length::FillPortion(sheet_pct))
.height(Length::Fill),
),
Element::from(space().width(Length::FillPortion(spacer_pct))),
)
} else {
(
Element::from(space().width(Length::FillPortion(spacer_pct))),
Element::from(
container(body)
.width(Length::FillPortion(sheet_pct))
.height(Length::Fill),
),
)
}
}
};
row![left_cell, right_cell]
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn portions(sheet_ratio: f32) -> (u16, u16) {
let sheet = (sheet_ratio * 100.0).round().clamp(0.0, 100.0) as u16;
let spacer = 100u16.saturating_sub(sheet);
if sheet == 0 {
(100, 1)
} else if spacer == 0 {
(1, 100)
} else {
(spacer, sheet)
}
}
fn sheet_surface_style(
theme: &iced::Theme,
edge: SheetEdge,
direction: LayoutDirection,
) -> iced::widget::container::Style {
use iced::{Background, Border, widget::container::Style};
let palette = theme.extended_palette();
let r = 12.0;
let radius = match edge {
SheetEdge::Bottom => Radius {
top_left: r,
top_right: r,
bottom_left: 0.0,
bottom_right: 0.0,
},
SheetEdge::Top => Radius {
top_left: 0.0,
top_right: 0.0,
bottom_left: r,
bottom_right: r,
},
SheetEdge::Start => match direction {
LayoutDirection::Ltr => Radius {
top_left: 0.0,
top_right: r,
bottom_left: 0.0,
bottom_right: r,
},
LayoutDirection::Rtl => Radius {
top_left: r,
top_right: 0.0,
bottom_left: r,
bottom_right: 0.0,
},
},
SheetEdge::End => match direction {
LayoutDirection::Ltr => Radius {
top_left: r,
top_right: 0.0,
bottom_left: r,
bottom_right: 0.0,
},
LayoutDirection::Rtl => Radius {
top_left: 0.0,
top_right: r,
bottom_left: 0.0,
bottom_right: r,
},
},
};
Style {
background: Some(Background::Color(palette.background.base.color)),
text_color: Some(palette.background.base.text),
border: Border {
radius,
width: 1.0,
color: palette.background.weak.color,
},
..Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn portions_handle_edges() {
assert_eq!(portions(0.5), (50, 50));
assert_eq!(portions(1.0 / 3.0), (67, 33));
assert_eq!(portions(2.0 / 3.0), (33, 67));
let (a, b) = portions(0.0);
assert!(a > 0 && b > 0);
let (a, b) = portions(1.0);
assert!(a > 0 && b > 0);
}
}