use std::any::{Any, type_name};
use iced_wgpu::core::{
Clipboard, Layout, Shell, Widget, layout, mouse, overlay, renderer,
widget::{Tree, tree},
};
use iced_widget::core::{Element, Event, Length, Point, Rectangle, Size, Vector};
use crate::ids::{Id, Ids};
const DEFAULT_PIN_SIZE: Size = Size::new(50.0, 20.0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PinSide {
#[default]
Left,
Right,
Top,
Bottom,
Row,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PinDirection {
Input,
Output,
#[default]
Both,
}
pub struct PinInfo<'a, I: Ids> {
direction: PinDirection,
pin_id: &'a I::PinId,
info: &'a I::Payload,
}
impl<'a, I: Ids> PinInfo<'a, I> {
pub(crate) fn new(direction: PinDirection, pin_id: &'a I::PinId, info: &'a I::Payload) -> Self {
Self {
direction,
pin_id,
info,
}
}
pub fn direction(&self) -> PinDirection {
self.direction
}
pub fn pin_id(&self) -> &I::PinId {
self.pin_id
}
pub fn info(&self) -> &I::Payload {
self.info
}
}
pub struct PinEnd<'a, I: Ids> {
node_id: &'a I::NodeId,
pin_id: &'a I::PinId,
direction: PinDirection,
info: &'a I::Payload,
is_occupied: bool,
}
impl<I: Ids> Clone for PinEnd<'_, I> {
fn clone(&self) -> Self {
*self
}
}
impl<I: Ids> Copy for PinEnd<'_, I> {}
impl<'a, I: Ids> PinEnd<'a, I> {
pub(crate) fn new(
node_id: &'a I::NodeId,
pin_id: &'a I::PinId,
direction: PinDirection,
info: &'a I::Payload,
is_occupied: bool,
) -> Self {
Self {
node_id,
pin_id,
direction,
info,
is_occupied,
}
}
pub fn node_id(&self) -> &I::NodeId {
self.node_id
}
pub fn pin_id(&self) -> &I::PinId {
self.pin_id
}
pub fn direction(&self) -> PinDirection {
self.direction
}
pub fn info(&self) -> &I::Payload {
self.info
}
pub fn is_occupied(&self) -> bool {
self.is_occupied
}
}
pub struct NodePin<'a, P, UI, Message, Theme, Renderer>
where
P: Id,
Renderer: renderer::Renderer,
{
side: PinSide,
direction: PinDirection,
pin_id: P,
user_info: UI,
content: Element<'a, Message, Theme, Renderer>,
interactions_disabled: bool,
}
impl<'a, P, Message, Theme, Renderer> NodePin<'a, P, (), Message, Theme, Renderer>
where
P: Id,
Renderer: renderer::Renderer,
{
pub fn new(
side: PinSide,
pin_id: P,
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self {
Self {
side,
direction: PinDirection::Both,
pin_id,
user_info: (),
content: content.into(),
interactions_disabled: false,
}
}
}
impl<'a, P, UI, Message, Theme, Renderer> NodePin<'a, P, UI, Message, Theme, Renderer>
where
P: Id,
Renderer: renderer::Renderer,
{
pub fn direction(mut self, direction: PinDirection) -> Self {
self.direction = direction;
self
}
pub fn info<UI2>(self, info: UI2) -> NodePin<'a, P, UI2, Message, Theme, Renderer> {
NodePin {
side: self.side,
direction: self.direction,
pin_id: self.pin_id,
user_info: info,
content: self.content,
interactions_disabled: self.interactions_disabled,
}
}
pub fn disable_interactions(mut self) -> Self {
self.interactions_disabled = true;
self
}
}
#[derive(Debug, Clone)]
pub(crate) struct NodePinState<P, UI> {
pub pin_id: P,
pub side: PinSide,
pub direction: PinDirection,
pub position: Point,
pub interactions_disabled: bool,
pub user_info: UI,
}
pub(crate) struct PinSlot {
state: Box<dyn Any>,
type_name: &'static str,
}
impl PinSlot {
fn new<P: 'static, UI: 'static>(state: NodePinState<P, UI>) -> Self {
Self {
state: Box::new(state),
type_name: type_name::<NodePinState<P, UI>>(),
}
}
pub(crate) fn get<P: 'static, UI: 'static>(&self) -> Option<&NodePinState<P, UI>> {
self.state.downcast_ref()
}
pub(crate) fn type_name(&self) -> &'static str {
self.type_name
}
fn get_mut<P: 'static, UI: 'static>(&mut self) -> &mut NodePinState<P, UI> {
self.state
.downcast_mut()
.expect("a pin's tree state is the state it registered")
}
}
impl<'a, P, UI, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for NodePin<'a, P, UI, Message, Theme, Renderer>
where
P: Id,
UI: Clone + 'static,
Renderer: renderer::Renderer + 'a,
Message: 'a,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<PinSlot>()
}
fn state(&self) -> tree::State {
tree::State::new(PinSlot::new(NodePinState {
pin_id: self.pin_id.clone(),
side: self.side,
direction: self.direction,
position: Point::new(0.0, 0.0),
interactions_disabled: self.interactions_disabled,
user_info: self.user_info.clone(),
}))
}
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn layout(
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
if let Some(content_tree) = tree.children.first_mut() {
let content_layout =
self.content
.as_widget_mut()
.layout(content_tree, renderer, limits);
let size = content_layout.size();
layout::Node::with_children(size, vec![content_layout])
} else {
layout::Node::new(DEFAULT_PIN_SIZE)
}
}
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::<PinSlot>().get_mut::<P, UI>();
state.pin_id = self.pin_id.clone();
state.side = self.side;
state.direction = self.direction;
state.position = layout.bounds().center();
state.interactions_disabled = self.interactions_disabled;
state.user_info = self.user_info.clone();
}
if let Some((child_layout, child_tree)) = layout.children().zip(&mut tree.children).next() {
self.content.as_widget_mut().update(
child_tree,
event,
child_layout,
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
if let Some((child_layout, child_tree)) = layout.children().zip(&tree.children).next() {
self.content.as_widget().draw(
child_tree,
renderer,
theme,
style,
child_layout,
cursor,
viewport,
);
}
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
if let Some((content_tree, content_layout)) =
tree.children.first().zip(layout.children().next())
{
self.content.as_widget().mouse_interaction(
content_tree,
content_layout,
cursor,
viewport,
renderer,
)
} else {
mouse::Interaction::default()
}
}
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>> {
let content_tree = tree.children.first_mut()?;
let content_layout = layout.children().next()?;
self.content.as_widget_mut().overlay(
content_tree,
content_layout,
renderer,
viewport,
translation,
)
}
fn size_hint(&self) -> Size<Length> {
self.content.as_widget().size_hint()
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(std::slice::from_ref(&self.content));
}
}
impl<'a, P, UI, Message, Theme, Renderer> From<NodePin<'a, P, UI, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
P: Id,
UI: Clone + 'static,
Theme: 'a,
Renderer: renderer::Renderer + 'a,
Message: 'a,
{
fn from(widget: NodePin<'a, P, UI, Message, Theme, Renderer>) -> Self {
Element::new(widget)
}
}
pub fn node_pin<'a, P, Message, Theme, Renderer>(
side: PinSide,
pin_id: P,
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> NodePin<'a, P, (), Message, Theme, Renderer>
where
P: Id,
Renderer: renderer::Renderer,
{
NodePin::new(side, pin_id, content)
}
#[macro_export]
macro_rules! pin {
($side:ident, $pin_id:expr, $content:expr, $dir:ident, $info:expr) => {
$crate::node_pin($crate::PinSide::$side, $pin_id, $content)
.direction($crate::PinDirection::$dir)
.info($info)
};
($side:ident, $pin_id:expr, $content:expr, $dir:ident) => {
$crate::node_pin($crate::PinSide::$side, $pin_id, $content)
.direction($crate::PinDirection::$dir)
};
($side:ident, $pin_id:expr, $content:expr) => {
$crate::node_pin($crate::PinSide::$side, $pin_id, $content)
};
}
#[cfg(test)]
mod tests {
use super::*;
use iced::widget::{text, text_input};
#[test]
fn content_tree_is_rebuilt_when_the_content_type_changes() {
let stateful: Element<'_, (), iced::Theme, iced::Renderer> =
node_pin(PinSide::Left, 0usize, text_input("", "x")).into();
let mut tree = Tree::new(&stateful);
let stateful_tag = tree.children[0].tag;
let stateless: Element<'_, (), iced::Theme, iced::Renderer> =
node_pin(PinSide::Left, 0usize, text("y")).into();
tree.diff(&stateless);
assert_eq!(tree.children.len(), 1);
assert_ne!(tree.children[0].tag, stateful_tag);
assert_eq!(tree.children[0].tag, Tree::new(&stateless).children[0].tag);
}
#[test]
fn the_pin_keeps_its_own_state_across_a_content_change() {
let before: Element<'_, (), iced::Theme, iced::Renderer> =
node_pin(PinSide::Left, 7usize, text("a")).into();
let mut tree = Tree::new(&before);
let after: Element<'_, (), iced::Theme, iced::Renderer> =
node_pin(PinSide::Left, 7usize, text_input("", "b")).into();
tree.diff(&after);
assert_eq!(tree.tag, tree::Tag::of::<PinSlot>());
let slot = tree.state.downcast_ref::<PinSlot>();
assert_eq!(slot.get::<usize, ()>().map(|s| s.pin_id), Some(7));
}
#[test]
fn a_pin_of_another_type_is_told_apart_by_its_slot() {
let pin: Element<'_, (), iced::Theme, iced::Renderer> =
node_pin(PinSide::Left, "named", text("a")).into();
let tree = Tree::new(&pin);
assert_eq!(tree.tag, tree::Tag::of::<PinSlot>());
let slot = tree.state.downcast_ref::<PinSlot>();
assert!(slot.get::<usize, ()>().is_none());
assert!(slot.get::<&'static str, ()>().is_some());
assert!(slot.type_name().contains("NodePinState"));
}
}