use std::cell::Cell;
use std::fmt;
use std::rc::Rc;
use crate::{ClipboardError, ClipboardReadFuture, DataTransferItem, Offset, ViewId};
#[derive(Clone)]
pub struct DragReport {
pub view_id: ViewId,
pub change: DragChange,
pub physical_position: Option<Offset>,
pub items: Vec<Rc<dyn DataTransferItem>>,
}
impl fmt::Debug for DragReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let types: Vec<&[String]> = self.items.iter().map(|item| item.types()).collect();
f.debug_struct("DragReport")
.field("view_id", &self.view_id)
.field("change", &self.change)
.field("physical_position", &self.physical_position)
.field("items", &types)
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DragChange {
Entered,
Moved,
Dropped,
Exited,
}
pub struct DraggedItems {
reading: Rc<Cell<Reading>>,
items: Vec<Rc<dyn DataTransferItem>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Reading {
NotDropped,
Dropping,
Over,
}
impl DraggedItems {
pub fn new(items: Vec<Box<dyn DataTransferItem>>) -> DraggedItems {
let reading = Rc::new(Cell::new(Reading::NotDropped));
let items = items
.into_iter()
.map(|item| {
Rc::new(DraggedItem {
item,
reading: Rc::clone(&reading),
}) as Rc<dyn DataTransferItem>
})
.collect();
DraggedItems { reading, items }
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn items(&self) -> Vec<Rc<dyn DataTransferItem>> {
self.items.clone()
}
pub fn report_drop<R>(&self, report: impl FnOnce() -> R) -> R {
self.reading.set(Reading::Dropping);
let _over = ReadingEnds(&self.reading);
report()
}
}
struct ReadingEnds<'a>(&'a Cell<Reading>);
impl Drop for ReadingEnds<'_> {
fn drop(&mut self) {
self.0.set(Reading::Over);
}
}
struct DraggedItem {
item: Box<dyn DataTransferItem>,
reading: Rc<Cell<Reading>>,
}
impl DataTransferItem for DraggedItem {
fn types(&self) -> &[String] {
self.item.types()
}
fn read(&self, mime: &str) -> ClipboardReadFuture {
match self.reading.get() {
Reading::NotDropped => Box::pin(std::future::ready(Err(ClipboardError::Denied))),
Reading::Dropping => self.item.read(mime),
Reading::Over => Box::pin(std::future::ready(Err(ClipboardError::Changed))),
}
}
}
#[cfg(test)]
mod tests {
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
use super::*;
struct Text([String; 1]);
impl Text {
fn item() -> Box<dyn DataTransferItem> {
Box::new(Text(["text/plain".to_owned()]))
}
}
impl DataTransferItem for Text {
fn types(&self) -> &[String] {
&self.0
}
fn read(&self, _mime: &str) -> ClipboardReadFuture {
Box::pin(std::future::ready(Ok(b"hi".to_vec())))
}
}
fn now<T>(mut future: Pin<Box<dyn Future<Output = T>>>) -> T {
match future
.as_mut()
.poll(&mut Context::from_waker(Waker::noop()))
{
Poll::Ready(output) => output,
Poll::Pending => panic!("the item answers in the call"),
}
}
#[test]
fn an_item_shows_its_types_while_the_drag_passes_over_but_not_its_content() {
let dragged = DraggedItems::new(vec![Text::item()]);
let items = dragged.items();
assert_eq!(items[0].types(), ["text/plain"]);
assert_eq!(
now(items[0].read("text/plain")),
Err(ClipboardError::Denied)
);
}
#[test]
fn an_item_is_read_while_its_drop_is_reported_and_not_after() {
let dragged = DraggedItems::new(vec![Text::item()]);
let items = dragged.items();
let read = dragged.report_drop(|| now(items[0].read("text/plain")));
assert_eq!(read, Ok(b"hi".to_vec()));
assert_eq!(
now(items[0].read("text/plain")),
Err(ClipboardError::Changed)
);
}
#[test]
fn a_read_asked_for_during_the_drop_completes_after_it() {
let dragged = DraggedItems::new(vec![Text::item()]);
let items = dragged.items();
let read = dragged.report_drop(|| items[0].read("text/plain"));
assert_eq!(now(read), Ok(b"hi".to_vec()));
}
#[test]
fn a_drag_carrying_nothing_is_empty() {
assert!(DraggedItems::new(Vec::new()).is_empty());
assert!(!DraggedItems::new(vec![Text::item()]).is_empty());
}
}