Skip to content

Instantly share code, notes, and snippets.

@Zomatree
Created July 29, 2026 16:39
Show Gist options
  • Select an option

  • Save Zomatree/a8b4fff011fb8ea223f5b1c772a8653a to your computer and use it in GitHub Desktop.

Select an option

Save Zomatree/a8b4fff011fb8ea223f5b1c772a8653a to your computer and use it in GitHub Desktop.
use std::{hash::Hash, rc::Rc};
use freya::prelude::*;
pub struct Reorder<T: 'static, B, DB> {
values: Writable<Vec<T>>,
builder: B,
drag_builder: Rc<DB>,
layout: LayoutData,
style: StyleState,
}
impl<T, B: Fn(&T) -> Element + 'static, DB: Fn(&T) -> Element + 'static> Reorder<T, B, DB> {
pub fn new(values: impl IntoWritable<Vec<T>>, builder: B, drag_builder: DB) -> Self {
Self {
values: values.into_writable(),
builder: builder,
drag_builder: Rc::new(drag_builder),
layout: LayoutData::default(),
style: StyleState::default(),
}
}
}
impl<T, B, DB> LayoutExt for Reorder<T, B, DB> {
fn get_layout(&mut self) -> &mut LayoutData {
&mut self.layout
}
}
impl<T, B, DB> ContainerExt for Reorder<T, B, DB> {}
impl<T, B, DB> ContainerWithContentExt for Reorder<T, B, DB> {}
impl<T, B, DB> PartialEq for Reorder<T, B, DB> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T: Clone + PartialEq + Hash, B: Fn(&T) -> Element + 'static, DB: Fn(&T) -> Element + 'static>
Component for Reorder<T, B, DB>
{
fn render(&self) -> impl IntoElement {
let mut state = use_state(|| self.values.read().cloned());
let mut selected = use_state(|| None);
let mut hovered = use_state(|| None);
let size = use_state(Size2D::default);
let mut container = rect();
*container.get_style() = self.style.clone();
container.layout(self.layout.clone()).children(
state
.read()
.cloned()
.into_iter()
.enumerate()
.map(|(i, value)| {
rect()
.key(&value)
.child(
DropZone::<T>::new(
{
DragZone::new(
value.clone(),
if hovered.read().as_ref().is_none_or(|(_, v)| {
v != &value || selected.read().as_ref() != Some(v)
}) {
rect()
.interactive(selected.read().is_none())
.child((self.builder)(&value))
.into_element()
} else {
rect()
.width(Size::px(size.read().width))
.height(Size::px(size.read().height))
.into_element()
},
)
.drag_threshold(2.)
.drag_element(
rect()
.on_sized({
let value = value.clone();
move |_| {
let is_dragging = selected.read().is_some();
if !is_dragging {
selected.set(Some(value.clone()));
hovered.set(Some((i, value.clone())));
}
}
})
.child(DraggedElement {
values: self.values.clone(),
state,
value,
builder: self.drag_builder.clone(),
size,
selected,
}),
)
.show_while_dragging(true)
.into_element()
},
|_| {},
)
.on_drag_over(move |hovering: bool| {
if !hovering {
return;
};
let current = hovered.read().cloned();
let selected = selected.read().cloned();
if let Some(current) = current
&& let Some(selected) = selected
{
state.write().remove(current.0);
state.write().insert(i, current.1.clone());
hovered.set(Some((i, selected)));
}
}),
)
.into_element()
}),
)
}
}
struct DraggedElement<T: 'static, B> {
pub values: Writable<Vec<T>>,
pub state: State<Vec<T>>,
pub value: T,
pub builder: Rc<B>,
pub size: State<Size2D>,
pub selected: State<Option<T>>,
}
impl<T: 'static, B> PartialEq for DraggedElement<T, B> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T: Clone + PartialEq, B: Fn(&T) -> Element + 'static> Component for DraggedElement<T, B> {
fn render(&self) -> impl IntoElement {
let mut values = self.values.clone();
let state = self.state;
let mut size = self.size;
let mut selected = self.selected;
use_drop(move || {
Cursor::set(CursorIcon::default());
selected.set(None);
values.set(state.read().cloned());
});
rect()
.on_sized(move |area: Event<SizedEventData>| {
Cursor::set(CursorIcon::Pointer);
size.set_if_modified(area.area.size)
})
.child((self.builder)(
self.selected.read().as_ref().unwrap_or(&self.value),
))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment