iced-yoda/core/src/clipboard.rs
2026-01-23 14:03:53 -05:00

247 lines
6.6 KiB
Rust

//! Access the clipboard.
use std::any::Any;
use dnd::{DndAction, DndDestinationRectangle, DndSurface};
use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData};
use crate::{Element, Vector, widget::tree::State, window};
#[derive(Debug)]
pub struct IconSurface<E> {
pub element: E,
pub state: State,
pub offset: Vector,
}
pub type DynIconSurface = IconSurface<Box<dyn Any>>;
impl<T: 'static, R: 'static> IconSurface<Element<'static, (), T, R>> {
pub fn new(
element: Element<'static, (), T, R>,
state: State,
offset: Vector,
) -> Self {
Self {
element,
state,
offset,
}
}
fn upcast(self) -> DynIconSurface {
IconSurface {
element: Box::new(self.element),
state: self.state,
offset: self.offset,
}
}
}
impl DynIconSurface {
/// Downcast `element` to concrete type `Element<(), T, R>`
///
/// Panics if type doesn't match
pub fn downcast<T: 'static, R: 'static>(
self,
) -> IconSurface<Element<'static, (), T, R>> {
IconSurface {
element: *self
.element
.downcast()
.expect("drag-and-drop icon surface has invalid element type"),
state: self.state,
offset: self.offset,
}
}
}
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
/// Reads the current content of the [`Clipboard`] as text.
fn read(&self, kind: Kind) -> Option<String>;
/// Writes the given text contents to the [`Clipboard`].
fn write(&mut self, kind: Kind, contents: String);
/// Consider using [`read_data`] instead
/// Reads the current content of the [`Clipboard`] as text.
fn read_data(
&self,
_kind: Kind,
_mimes: Vec<String>,
) -> Option<(Vec<u8>, String)> {
None
}
/// Writes the given contents to the [`Clipboard`].
fn write_data(
&mut self,
_kind: Kind,
_contents: ClipboardStoreData<
Box<dyn Send + Sync + 'static + mime::AsMimeTypes>,
>,
) {
}
/// Starts a DnD operation.
fn register_dnd_destination(
&self,
_surface: DndSurface,
_rectangles: Vec<DndDestinationRectangle>,
) {
}
/// Set the final action for the DnD operation.
/// Only should be done if it is requested.
fn set_action(&self, _action: DndAction) {}
/// Registers Dnd destinations
fn start_dnd(
&mut self,
_internal: bool,
_source_surface: Option<DndSource>,
_icon_surface: Option<DynIconSurface>,
_content: Box<dyn AsMimeTypes + Send + 'static>,
_actions: DndAction,
) {
}
/// Ends a DnD operation.
fn end_dnd(&self) {}
/// Consider using [`peek_dnd`] instead
/// Peeks the data on the DnD with a specific mime type.
/// Will return an error if there is no ongoing DnD operation.
fn peek_dnd(&self, _mime: String) -> Option<(Vec<u8>, String)> {
None
}
/// Request window size
fn request_logical_window_size(&self, _width: f32, _height: f32) {}
}
/// The kind of [`Clipboard`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
/// The standard clipboard.
Standard,
/// The primary clipboard.
///
/// Normally only present in X11 and Wayland.
Primary,
}
/// Starts a DnD operation.
/// icon surface is a tuple of the icon element and optionally the icon element state.
pub fn start_dnd<T: 'static, R: 'static>(
clipboard: &mut dyn Clipboard,
internal: bool,
source_surface: Option<DndSource>,
icon_surface: Option<IconSurface<Element<'static, (), T, R>>>,
content: Box<dyn AsMimeTypes + Send + 'static>,
actions: DndAction,
) {
clipboard.start_dnd(
internal,
source_surface,
icon_surface.map(IconSurface::upcast),
content,
actions,
);
}
/// A null implementation of the [`Clipboard`] trait.
#[derive(Debug, Clone, Copy)]
pub struct Null;
impl Clipboard for Null {
fn read(&self, _kind: Kind) -> Option<String> {
None
}
fn write(&mut self, _kind: Kind, _contents: String) {}
}
/// Reads the current content of the [`Clipboard`].
pub fn read_data<T: AllowedMimeTypes>(
clipboard: &mut dyn Clipboard,
) -> Option<T> {
clipboard
.read_data(Kind::Standard, T::allowed().into())
.and_then(|data| T::try_from(data).ok())
}
/// Reads the current content of the primary [`Clipboard`].
pub fn read_primary_data<T: AllowedMimeTypes>(
clipboard: &mut dyn Clipboard,
) -> Option<T> {
clipboard
.read_data(Kind::Primary, T::allowed().into())
.and_then(|data| T::try_from(data).ok())
}
/// Reads the current content of the primary [`Clipboard`].
pub fn peek_dnd<T: AllowedMimeTypes>(
clipboard: &mut dyn Clipboard,
mime: Option<String>,
) -> Option<T> {
let mime = mime.or_else(|| T::allowed().first().cloned())?;
clipboard
.peek_dnd(mime)
.and_then(|data| T::try_from(data).ok())
}
/// Source of a DnD operation.
#[derive(Debug, Clone)]
pub enum DndSource {
/// A widget is the source of the DnD operation.
Widget(crate::id::Id),
/// A surface is the source of the DnD operation.
Surface(window::Id),
}
/// A list of DnD destination rectangles.
#[derive(Debug, Clone, Default)]
pub struct DndDestinationRectangles {
/// The rectangle of the DnD destination.
rectangles: Vec<DndDestinationRectangle>,
}
impl DndDestinationRectangles {
/// Creates a new [`DndDestinationRectangles`].
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`DndDestinationRectangles`] with the given capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self {
rectangles: Vec::with_capacity(capacity),
}
}
/// Pushes a new rectangle to the list of DnD destination rectangles.
pub fn push(&mut self, rectangle: DndDestinationRectangle) {
self.rectangles.push(rectangle);
}
/// Appends the list of DnD destination rectangles to the current list.
pub fn append(&mut self, other: &mut Vec<DndDestinationRectangle>) {
self.rectangles.append(other);
}
/// Returns the list of DnD destination rectangles.
/// This consumes the [`DndDestinationRectangles`].
pub fn into_rectangles(mut self) -> Vec<DndDestinationRectangle> {
self.rectangles.reverse();
self.rectangles
}
}
impl AsRef<[DndDestinationRectangle]> for DndDestinationRectangles {
fn as_ref(&self) -> &[DndDestinationRectangle] {
&self.rectangles
}
}