use crate::core::{
Border, Clipboard, Color, Element, Event, Length, Padding, Rectangle, Shadow, Shell, Size,
Theme, Vector, alignment,
layout::{self, Layout},
mouse, overlay, renderer, touch,
widget::{Operation, Tree, Widget, tree},
};
use iced_widget::core::{Alignment, Pixels, border::Radius};
pub const BORDER_WIDTH: f32 = 0.25;
pub const PADDING: Padding = Padding::new(2.0);
pub struct Cell<'a, Message, Theme, Renderer>
where
Renderer: renderer::Renderer + 'a,
Theme: Catalog + 'a,
{
content: Element<'a, Message, Theme, Renderer>,
height: Length,
width: Length,
padding: Padding,
align_x: Alignment,
align_y: Alignment,
on_press: Option<Message>,
class: Theme::Class<'a>, style_options: StyleOptions,
}
impl<'a, Message, Theme, Renderer> Cell<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Renderer: renderer::Renderer + 'a,
Theme: Catalog + 'a,
{
pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
Cell {
content: content.into(),
height: Length::Fill,
width: Length::Fill,
padding: PADDING,
align_x: Alignment::Start,
align_y: Alignment::Start,
on_press: None,
class: Theme::default(),
style_options: StyleOptions::default(),
}
}
pub fn height(mut self, length: Length) -> Self {
self.height = length;
self
}
pub fn width(mut self, length: Length) -> Self {
self.width = length;
self
}
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
pub fn align_x(mut self, alignment: impl Into<alignment::Alignment>) -> Self {
self.align_x = alignment.into();
self
}
pub fn align_y(mut self, alignment: impl Into<alignment::Alignment>) -> Self {
self.align_y = alignment.into();
self
}
pub fn on_press(mut self, message: Message) -> Self {
self.on_press = Some(message);
self
}
pub fn border_width(mut self, width: impl Into<Pixels>) -> Self
where
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
{
self.style_options.border_width = Some(width.into().0);
self
}
pub fn color(mut self, component: Component, color: Color) -> Self {
match component {
Component::Background => self.style_options.background = Some(color),
Component::Border => self.style_options.border = Some(color),
#[allow(unreachable_patterns)]
_ => {}
}
self
}
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
where
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
{
self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
self
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Cell<'_, Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
Theme: Catalog,
Message: std::clone::Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::default())
}
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(std::slice::from_ref(&self.content));
}
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
height: Length::Fill,
}
}
fn layout(
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout::positioned(
&limits,
self.width,
self.height,
self.padding,
|limits| {
self.content
.as_widget_mut()
.layout(&mut tree.children[0], renderer, limits)
},
|node, size| node.align(self.align_x.into(), self.align_y.into(), size),
)
}
fn operate(
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds());
operation.traverse(&mut |operation| {
self.content.as_widget_mut().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
operation,
);
});
}
fn update(
&mut self,
tree: &mut Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_mut::<State>();
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if self.on_press.is_some() && cursor.is_over(layout.bounds()) {
let state = tree.state.downcast_mut::<State>();
state.is_pressed = true;
shell.capture_event();
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
if state.is_pressed {
state.is_pressed = false;
if let Some(on_press) = &self.on_press
&& cursor.is_over(layout.bounds())
{
shell.publish(on_press.clone());
}
shell.capture_event();
}
}
Event::Touch(touch::Event::FingerLost { .. }) => {
let state = tree.state.downcast_mut::<State>();
state.is_pressed = false;
}
_ => {}
}
if !shell.is_event_captured() {}
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let widget_style = theme.style(&self.class);
let background = self
.style_options
.background
.unwrap_or(widget_style.background);
let border = self.style_options.border.unwrap_or(widget_style.border);
let border_width = self
.style_options
.border_width
.unwrap_or(widget_style.border_width);
let bounds = layout.bounds();
if let Some(clipped_viewport) = bounds.intersection(viewport) {
renderer.fill_quad(
renderer::Quad {
bounds: clipped_viewport,
border: Border {
color: border,
width: border_width,
radius: Radius::new(0.0),
},
shadow: Shadow::default(),
snap: false,
},
background,
);
renderer.with_layer(bounds, |renderer| {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
renderer_style,
layout.children().next().unwrap(),
cursor,
&clipped_viewport,
)
});
}
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'b>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
viewport,
translation,
)
}
}
impl<'a, Message, Theme, Renderer> From<Cell<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Renderer: renderer::Renderer + 'a,
Theme: Catalog + 'a,
{
fn from(cell: Cell<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
Element::new(cell)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
is_pressed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
pub background: Color,
pub border: Color,
pub border_width: f32,
}
impl Default for Style {
fn default() -> Self {
Self {
background: Color::default(),
border: Color::default(),
border_width: f32::default(),
}
}
}
#[derive(Default, Clone, Copy, PartialEq)]
pub(crate) struct StyleOptions {
pub background: Option<Color>,
pub border: Option<Color>,
pub border_width: Option<f32>,
}
pub enum Component {
Background,
Border,
}
pub trait Catalog {
type Class<'a>;
fn default<'a>() -> <Self as Catalog>::Class<'a>;
fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
}
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> StyleFn<'a, Self> {
Box::new(default)
}
fn style(&self, class: &StyleFn<'_, Self>) -> Style {
class(self)
}
}
pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette();
Style {
background: palette.background.base.color,
border: palette.primary.base.text,
border_width: BORDER_WIDTH,
}
}