Merge pull request #132 from hecrj/feature/read-clipboard

Clipboard access
This commit is contained in:
Héctor Ramón 2019-12-19 17:43:25 +01:00 committed by GitHub
commit 773a23630b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 172 additions and 60 deletions

View File

@ -1,15 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
before_install:
- |
if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
sudo apt-get -qq update
sudo apt-get install -y libasound2-dev libudev-dev
fi

View File

@ -1,5 +1,5 @@
# Iced # Iced
[![Build Status](https://travis-ci.org/hecrj/iced.svg?branch=master)](https://travis-ci.org/hecrj/iced) [![Test Status](https://github.com/hecrj/iced/workflows/Test/badge.svg)](https://github.com/hecrj/iced/actions)
[![Documentation](https://docs.rs/iced/badge.svg)][documentation] [![Documentation](https://docs.rs/iced/badge.svg)][documentation]
[![Crates.io](https://img.shields.io/crates/v/iced.svg)](https://crates.io/crates/iced) [![Crates.io](https://img.shields.io/crates/v/iced.svg)](https://crates.io/crates/iced)
[![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/hecrj/iced/blob/master/LICENSE) [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/hecrj/iced/blob/master/LICENSE)

8
native/src/clipboard.rs Normal file
View File

@ -0,0 +1,8 @@
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
/// Returns the current content of the [`Clipboard`] as text.
///
/// [`Clipboard`]: trait.Clipboard.html
fn content(&self) -> Option<String>;
}

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
layout, renderer, Color, Event, Hasher, Layout, Length, Point, Widget, layout, renderer, Clipboard, Color, Event, Hasher, Layout, Length, Point,
Widget,
}; };
/// A generic [`Widget`]. /// A generic [`Widget`].
@ -293,6 +294,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<B>, messages: &mut Vec<B>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
let mut original_messages = Vec::new(); let mut original_messages = Vec::new();
@ -302,6 +304,7 @@ where
cursor_position, cursor_position,
&mut original_messages, &mut original_messages,
renderer, renderer,
clipboard,
); );
original_messages original_messages
@ -366,6 +369,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
self.element.widget.on_event( self.element.widget.on_event(
event, event,
@ -373,6 +377,7 @@ where
cursor_position, cursor_position,
messages, messages,
renderer, renderer,
clipboard,
) )
} }

View File

@ -45,6 +45,7 @@ pub mod renderer;
pub mod subscription; pub mod subscription;
pub mod widget; pub mod widget;
mod clipboard;
mod element; mod element;
mod event; mod event;
mod hasher; mod hasher;
@ -57,6 +58,7 @@ pub use iced_core::{
Point, Rectangle, Vector, VerticalAlignment, Point, Rectangle, Vector, VerticalAlignment,
}; };
pub use clipboard::Clipboard;
pub use element::Element; pub use element::Element;
pub use event::Event; pub use event::Event;
pub use hasher::Hasher; pub use hasher::Hasher;

View File

@ -1,4 +1,6 @@
use crate::{input::mouse, layout, Element, Event, Layout, Point, Size}; use crate::{
input::mouse, layout, Clipboard, Element, Event, Layout, Point, Size,
};
use std::hash::Hasher; use std::hash::Hasher;
@ -185,7 +187,7 @@ where
/// ); /// );
/// ///
/// // Update the user interface /// // Update the user interface
/// let messages = user_interface.update(&renderer, events.drain(..)); /// let messages = user_interface.update(&renderer, None, events.drain(..));
/// ///
/// cache = user_interface.into_cache(); /// cache = user_interface.into_cache();
/// ///
@ -198,6 +200,7 @@ where
pub fn update( pub fn update(
&mut self, &mut self,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
events: impl Iterator<Item = Event>, events: impl Iterator<Item = Event>,
) -> Vec<Message> { ) -> Vec<Message> {
let mut messages = Vec::new(); let mut messages = Vec::new();
@ -213,6 +216,7 @@ where
self.cursor_position, self.cursor_position,
&mut messages, &mut messages,
renderer, renderer,
clipboard,
); );
} }
@ -282,7 +286,7 @@ where
/// &mut renderer, /// &mut renderer,
/// ); /// );
/// ///
/// let messages = user_interface.update(&renderer, events.drain(..)); /// let messages = user_interface.update(&renderer, None, events.drain(..));
/// ///
/// // Draw the user interface /// // Draw the user interface
/// let mouse_cursor = user_interface.draw(&mut renderer); /// let mouse_cursor = user_interface.draw(&mut renderer);

View File

@ -24,12 +24,12 @@ pub mod button;
pub mod checkbox; pub mod checkbox;
pub mod column; pub mod column;
pub mod container; pub mod container;
pub mod svg;
pub mod image; pub mod image;
pub mod radio; pub mod radio;
pub mod row; pub mod row;
pub mod scrollable; pub mod scrollable;
pub mod slider; pub mod slider;
pub mod svg;
pub mod text; pub mod text;
pub mod text_input; pub mod text_input;
@ -58,7 +58,7 @@ pub use text::Text;
#[doc(no_inline)] #[doc(no_inline)]
pub use text_input::TextInput; pub use text_input::TextInput;
use crate::{layout, Event, Hasher, Layout, Length, Point}; use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point};
/// A component that displays information and allows interaction. /// A component that displays information and allows interaction.
/// ///
@ -142,6 +142,7 @@ where
_cursor_position: Point, _cursor_position: Point,
_messages: &mut Vec<Message>, _messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) { ) {
} }
} }

View File

@ -6,8 +6,8 @@
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, Background, Element, Event, Hasher, Layout, Length, Point, layout, Background, Clipboard, Element, Event, Hasher, Layout, Length,
Rectangle, Widget, Point, Rectangle, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
@ -192,6 +192,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) { ) {
match event { match event {
Event::Mouse(mouse::Event::Input { Event::Mouse(mouse::Event::Input {

View File

@ -3,7 +3,7 @@ use std::hash::Hash;
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, row, text, Align, Color, Element, Event, Font, Hasher, layout, row, text, Align, Clipboard, Color, Element, Event, Font, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget, VerticalAlignment, Widget,
}; };
@ -114,6 +114,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) { ) {
match event { match event {
Event::Mouse(mouse::Event::Input { Event::Mouse(mouse::Event::Input {

View File

@ -2,7 +2,8 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{ use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Widget,
}; };
use std::u32; use std::u32;
@ -153,6 +154,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
self.children.iter_mut().zip(layout.children()).for_each( self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| { |(child, layout)| {
@ -162,6 +164,7 @@ where
cursor_position, cursor_position,
messages, messages,
renderer, renderer,
clipboard,
) )
}, },
); );

View File

@ -2,7 +2,8 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{ use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Widget,
}; };
use std::u32; use std::u32;
@ -131,6 +132,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
self.content.widget.on_event( self.content.widget.on_event(
event, event,
@ -138,6 +140,7 @@ where
cursor_position, cursor_position,
messages, messages,
renderer, renderer,
clipboard,
) )
} }

View File

@ -1,7 +1,7 @@
//! Create choices using radio buttons. //! Create choices using radio buttons.
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, row, text, Align, Color, Element, Event, Font, Hasher, layout, row, text, Align, Clipboard, Color, Element, Event, Font, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget, VerticalAlignment, Widget,
}; };
@ -113,6 +113,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) { ) {
match event { match event {
Event::Mouse(mouse::Event::Input { Event::Mouse(mouse::Event::Input {

View File

@ -2,7 +2,8 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{ use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Widget,
}; };
use std::u32; use std::u32;
@ -154,6 +155,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
self.children.iter_mut().zip(layout.children()).for_each( self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| { |(child, layout)| {
@ -163,6 +165,7 @@ where
cursor_position, cursor_position,
messages, messages,
renderer, renderer,
clipboard,
) )
}, },
); );

View File

@ -2,8 +2,8 @@
use crate::{ use crate::{
column, column,
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, Align, Column, Element, Event, Hasher, Layout, Length, Point, layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length,
Rectangle, Size, Widget, Point, Rectangle, Size, Widget,
}; };
use std::{f32, hash::Hash, u32}; use std::{f32, hash::Hash, u32};
@ -143,6 +143,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
@ -247,6 +248,7 @@ where
cursor_position, cursor_position,
messages, messages,
renderer, renderer,
clipboard,
) )
} }

View File

@ -6,8 +6,8 @@
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Widget, Rectangle, Size, Widget,
}; };
use std::{hash::Hash, ops::RangeInclusive}; use std::{hash::Hash, ops::RangeInclusive};
@ -133,6 +133,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) { ) {
let mut change = || { let mut change = || {
let bounds = layout.bounds(); let bounds = layout.bounds();

View File

@ -6,8 +6,8 @@
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::{ use crate::{
input::{keyboard, mouse, ButtonState}, input::{keyboard, mouse, ButtonState},
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Widget, Rectangle, Size, Widget,
}; };
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -172,6 +172,7 @@ where
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) { ) {
match event { match event {
Event::Mouse(mouse::Event::Input { Event::Mouse(mouse::Event::Input {
@ -209,7 +210,9 @@ where
} }
} }
Event::Keyboard(keyboard::Event::CharacterReceived(c)) Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused && !c.is_control() => if self.state.is_focused
&& self.state.is_pasting.is_none()
&& !c.is_control() =>
{ {
let cursor_position = self.state.cursor_position(&self.value); let cursor_position = self.state.cursor_position(&self.value);
@ -254,26 +257,18 @@ where
} }
} }
keyboard::KeyCode::Left => { keyboard::KeyCode::Left => {
let jump_modifier_pressed = if cfg!(target_os = "macos") { if platform::is_jump_modifier_pressed(modifiers)
modifiers.alt && !self.is_secure
} else { {
modifiers.control
};
if jump_modifier_pressed && !self.is_secure {
self.state.move_cursor_left_by_words(&self.value); self.state.move_cursor_left_by_words(&self.value);
} else { } else {
self.state.move_cursor_left(&self.value); self.state.move_cursor_left(&self.value);
} }
} }
keyboard::KeyCode::Right => { keyboard::KeyCode::Right => {
let jump_modifier_pressed = if cfg!(target_os = "macos") { if platform::is_jump_modifier_pressed(modifiers)
modifiers.alt && !self.is_secure
} else { {
modifiers.control
};
if jump_modifier_pressed && !self.is_secure {
self.state.move_cursor_right_by_words(&self.value); self.state.move_cursor_right_by_words(&self.value);
} else { } else {
self.state.move_cursor_right(&self.value); self.state.move_cursor_right(&self.value);
@ -285,6 +280,50 @@ where
keyboard::KeyCode::End => { keyboard::KeyCode::End => {
self.state.move_cursor_to_end(&self.value); self.state.move_cursor_to_end(&self.value);
} }
keyboard::KeyCode::V => {
if platform::is_copy_paste_modifier_pressed(modifiers) {
if let Some(clipboard) = clipboard {
let content = match self.state.is_pasting.take() {
Some(content) => content,
None => {
let content: String = clipboard
.content()
.unwrap_or(String::new())
.chars()
.filter(|c| !c.is_control())
.collect();
Value::new(&content)
}
};
let cursor_position =
self.state.cursor_position(&self.value);
self.value
.insert_many(cursor_position, content.clone());
self.state.cursor_position += content.len();
self.state.is_pasting = Some(content);
let message =
(self.on_change)(self.value.to_string());
messages.push(message);
}
} else {
self.state.is_pasting = None;
}
}
_ => {}
},
Event::Keyboard(keyboard::Event::Input {
key_code,
state: ButtonState::Released,
..
}) => match key_code {
keyboard::KeyCode::V => {
self.state.is_pasting = None;
}
_ => {} _ => {}
}, },
_ => {} _ => {}
@ -397,6 +436,7 @@ where
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct State { pub struct State {
is_focused: bool, is_focused: bool,
is_pasting: Option<Value>,
cursor_position: usize, cursor_position: usize,
} }
@ -416,6 +456,7 @@ impl State {
Self { Self {
is_focused: true, is_focused: true,
is_pasting: None,
cursor_position: usize::MAX, cursor_position: usize::MAX,
} }
} }
@ -486,7 +527,7 @@ impl State {
/// ///
/// [`TextInput`]: struct.TextInput.html /// [`TextInput`]: struct.TextInput.html
// TODO: Reduce allocations, cache results (?) // TODO: Reduce allocations, cache results (?)
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Value { pub struct Value {
graphemes: Vec<String>, graphemes: Vec<String>,
} }
@ -573,8 +614,6 @@ impl Value {
} }
/// Inserts a new `char` at the given grapheme `index`. /// Inserts a new `char` at the given grapheme `index`.
///
/// [`Value`]: struct.Value.html
pub fn insert(&mut self, index: usize, c: char) { pub fn insert(&mut self, index: usize, c: char) {
self.graphemes.insert(index, c.to_string()); self.graphemes.insert(index, c.to_string());
@ -584,6 +623,13 @@ impl Value {
.collect(); .collect();
} }
/// Inserts a bunch of graphemes at the given grapheme `index`.
pub fn insert_many(&mut self, index: usize, mut value: Value) {
let _ = self
.graphemes
.splice(index..index, value.graphemes.drain(..));
}
/// Removes the grapheme at the given `index`. /// Removes the grapheme at the given `index`.
/// ///
/// [`Value`]: struct.Value.html /// [`Value`]: struct.Value.html
@ -656,3 +702,27 @@ fn find_cursor_position<Renderer: self::Renderer>(
) )
} }
} }
mod platform {
use crate::input::keyboard;
pub fn is_jump_modifier_pressed(
modifiers: keyboard::ModifiersState,
) -> bool {
if cfg!(target_os = "macos") {
modifiers.alt
} else {
modifiers.control
}
}
pub fn is_copy_paste_modifier_pressed(
modifiers: keyboard::ModifiersState,
) -> bool {
if cfg!(target_os = "macos") {
modifiers.logo
} else {
modifiers.control
}
}
}

View File

@ -13,6 +13,7 @@ debug = []
[dependencies] [dependencies]
iced_native = { version = "0.1.0-alpha", path = "../native" } iced_native = { version = "0.1.0-alpha", path = "../native" }
winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"}
window_clipboard = { git = "https://github.com/hecrj/window_clipboard", rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" }
futures = { version = "0.3", features = ["thread-pool"] } futures = { version = "0.3", features = ["thread-pool"] }
log = "0.4" log = "0.4"

View File

@ -2,8 +2,8 @@ use crate::{
conversion, conversion,
input::{keyboard, mouse}, input::{keyboard, mouse},
renderer::{Target, Windowed}, renderer::{Target, Windowed},
subscription, Cache, Command, Container, Debug, Element, Event, Length, subscription, Cache, Clipboard, Command, Container, Debug, Element, Event,
MouseCursor, Settings, Subscription, UserInterface, Length, MouseCursor, Settings, Subscription, UserInterface,
}; };
/// An interactive, native cross-platform application. /// An interactive, native cross-platform application.
@ -139,6 +139,7 @@ pub trait Application: Sized {
let mut size = window.inner_size(); let mut size = window.inner_size();
let mut resized = false; let mut resized = false;
let clipboard = Clipboard::new(&window);
let mut renderer = Self::Renderer::new(); let mut renderer = Self::Renderer::new();
let mut target = { let mut target = {
@ -193,8 +194,13 @@ pub trait Application: Sized {
subscription_pool.broadcast_event(*event) subscription_pool.broadcast_event(*event)
}); });
let mut messages = let mut messages = user_interface.update(
user_interface.update(&renderer, events.drain(..)); &renderer,
clipboard
.as_ref()
.map(|c| c as &dyn iced_native::Clipboard),
events.drain(..),
);
messages.extend(external_messages.drain(..)); messages.extend(external_messages.drain(..));
debug.event_processing_finished(); debug.event_processing_finished();

13
winit/src/clipboard.rs Normal file
View File

@ -0,0 +1,13 @@
pub struct Clipboard(window_clipboard::Clipboard);
impl Clipboard {
pub fn new(window: &winit::window::Window) -> Option<Clipboard> {
window_clipboard::Clipboard::new(window).map(Clipboard).ok()
}
}
impl iced_native::Clipboard for Clipboard {
fn content(&self) -> Option<String> {
self.0.read().ok()
}
}

View File

@ -29,11 +29,9 @@ pub mod conversion;
pub mod settings; pub mod settings;
mod application; mod application;
mod clipboard;
mod subscription; mod subscription;
pub use application::Application;
pub use settings::Settings;
// We disable debug capabilities on release builds unless the `debug` feature // We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled. // is explicitly enabled.
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
@ -43,4 +41,8 @@ mod debug;
#[path = "debug/null.rs"] #[path = "debug/null.rs"]
mod debug; mod debug;
pub use application::Application;
pub use settings::Settings;
use clipboard::Clipboard;
use debug::Debug; use debug::Debug;