Input copy/paste works now

Also updated to wgpu 0.18.1
This commit is contained in:
Jonathan Johnson 2023-11-15 14:25:59 -08:00
parent 70eecb7429
commit 1ed1a95a1d
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 339 additions and 88 deletions

202
Cargo.lock generated
View file

@ -121,6 +121,25 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arboard"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac57f2b058a76363e357c056e4f74f1945bf734d37b8b3ef49066c4787dde0fc"
dependencies = [
"clipboard-win",
"core-graphics",
"image",
"log",
"objc",
"objc-foundation",
"objc_id",
"parking_lot",
"thiserror",
"winapi",
"x11rb 0.10.1",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@ -335,6 +354,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "clipboard-win"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -446,6 +476,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "cursor-icon"
version = "1.1.0"
@ -529,14 +568,24 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "etagere"
version = "0.2.8"
@ -562,6 +611,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
[[package]]
name = "fdeflate"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868"
dependencies = [
"simd-adler32",
]
[[package]]
name = "figures"
version = "0.1.0"
@ -574,6 +632,16 @@ dependencies = [
"winit",
]
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "float_next_after"
version = "1.0.0"
@ -675,6 +743,16 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "gethostname"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "gethostname"
version = "0.3.0"
@ -742,6 +820,7 @@ version = "0.1.0"
dependencies = [
"ahash",
"alot",
"arboard",
"derive_more",
"gooey-macros",
"intentional",
@ -880,6 +959,8 @@ dependencies = [
"color_quant",
"num-rational",
"num-traits",
"png",
"tiff",
]
[[package]]
@ -965,6 +1046,12 @@ dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
[[package]]
name = "js-sys"
version = "0.3.65"
@ -1260,13 +1347,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "naga"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61d829abac9f5230a85d8cc83ec0879b4c09790208ae25b5ea031ef84562e071"
checksum = "6cd05939c491da968a42986204b7431678be21fdcd4b10cc84997ba130ada5a4"
dependencies = [
"bit-set",
"bitflags 2.4.1",
@ -1321,6 +1409,18 @@ dependencies = [
"jni-sys",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset 0.6.5",
]
[[package]]
name = "nix"
version = "0.25.1"
@ -1428,6 +1528,17 @@ dependencies = [
"objc_exception",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc-sys"
version = "0.3.1"
@ -1459,6 +1570,15 @@ dependencies = [
"cc",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.32.1"
@ -1610,6 +1730,19 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "png"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "pollster"
version = "0.3.0"
@ -1935,6 +2068,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "similar"
version = "2.3.0"
@ -2018,6 +2157,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strict-num"
version = "0.1.1"
@ -2099,6 +2244,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tiff"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "tiny-skia"
version = "0.11.2"
@ -2496,6 +2652,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "wgpu"
version = "0.18.0"
@ -2523,9 +2685,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "837e02ddcdc6d4a9b56ba4598f7fd4202a7699ab03f6ef4dcdebfad2c966aea6"
checksum = "ef91c1d62d1e9e81c79e600131a258edf75c9531cbdbde09c44a011a47312726"
dependencies = [
"arrayvec",
"bit-vec",
@ -2838,7 +3000,7 @@ dependencies = [
"web-time",
"windows-sys 0.48.0",
"x11-dl",
"x11rb",
"x11rb 0.12.0",
"xkbcommon-dl",
]
@ -2862,6 +3024,19 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "x11rb"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
dependencies = [
"gethostname 0.2.3",
"nix 0.24.3",
"winapi",
"winapi-wsapoll",
"x11rb-protocol 0.10.0",
]
[[package]]
name = "x11rb"
version = "0.12.0"
@ -2869,14 +3044,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
dependencies = [
"as-raw-xcb-connection",
"gethostname",
"gethostname 0.3.0",
"libc",
"libloading 0.7.4",
"nix 0.26.4",
"once_cell",
"winapi",
"winapi-wsapoll",
"x11rb-protocol",
"x11rb-protocol 0.12.0",
]
[[package]]
name = "x11rb-protocol"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
dependencies = [
"nix 0.24.3",
]
[[package]]

View file

@ -26,6 +26,7 @@ palette = "0.7.3"
ahash = "0.8.6"
gooey-macros = { version = "0.1.0", path = "gooey-macros" }
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
arboard = "3.2.1"
# [patch."https://github.com/khonsulabs/kludgine"]

View file

@ -118,6 +118,101 @@ impl Input {
editor.set_select_opt(Some(Cursor::new_with_affinity(0, 0, Affinity::Before)));
}
}
fn handle_key(
&mut self,
input: KeyEvent,
context: &mut EventContext<'_, '_>,
) -> (bool, EventHandling) {
let editor = self.editor_mut(context.kludgine, &context.widget);
match (input.state, input.logical_key, input.text.as_deref()) {
(ElementState::Pressed, key @ (Key::Backspace | Key::Delete), _) => {
editor.action(
context.kludgine.font_system(),
match key {
Key::Backspace => Action::Backspace,
Key::Delete => Action::Delete,
_ => unreachable!("previously matched"),
},
);
(true, HANDLED)
}
(ElementState::Pressed, key @ (Key::ArrowLeft | Key::ArrowDown | Key::ArrowUp | Key::ArrowRight), _) => {
let modifiers = context.modifiers();
match (editor.select_opt(), modifiers.state().shift_key()) {
(None, true) => {
editor.set_select_opt(Some(editor.cursor()));
}
(Some(_), false) => {
editor.set_select_opt(None);
}
_ => {}
};
editor.action(
context.kludgine.font_system(),
match key {
Key::ArrowLeft if modifiers.word_select() => Action::PreviousWord,
Key::ArrowLeft => Action::Left,
Key::ArrowDown => Action::Down,
Key::ArrowUp => Action::Up,
Key::ArrowRight if modifiers.word_select() => Action::NextWord,
Key::ArrowRight => Action::Right,
_ => unreachable!("previously matched"),
},
);
(false, HANDLED)
}
(state, _, Some("a")) if context.modifiers().primary() => {
if state.is_pressed() {
self.select_all();
}
(false, HANDLED)
}
(state, _, Some("c")) if context.modifiers().primary() => {
if state.is_pressed() {
if let Some(mut clipboard) = context.clipboard_guard() {
if let Some(selection) = editor.copy_selection() {
match clipboard.set_text(selection) {
Ok(()) => {},
Err(err) => tracing::error!("error copying to clipboard: {err}"),
}
}
}
}
(false, HANDLED)
}
(state, _, Some("v")) if context.modifiers().primary() => {
let pasted = state.is_pressed() &&
match context.clipboard_guard().map(|mut clipboard| clipboard.get_text()) {
Some(Ok(text)) => {
editor.insert_string(&text, None);
true
},
None | Some(Err(arboard::Error::ConversionFailure)) => false,
Some(Err(err)) => {tracing::error!("error retrieving clipboard contents: {err}"); false},
}
;
(pasted, HANDLED)
}
(state, _, Some(text))
if !context.modifiers().primary()
&& text != "\t" // tab
&& text != "\r" // enter/return
&& text != "\u{1b}" // escape
=>
{
if state.is_pressed() {
editor.insert_string(text, None);
}
(state.is_pressed(), HANDLED)
}
(_, _, _) => (false, IGNORED),
}
}
}
impl Default for Input {
@ -389,70 +484,11 @@ impl Widget for Input {
on_key.invoke(input.clone())?;
}
let editor = self.editor_mut(context.kludgine, &context.widget);
// println!(
// "Keyboard input: {:?}. {:?}, {:?}",
// input.logical_key, input.text, input.physical_key
// );
let (text_changed, handled) = match (input.state, input.logical_key, input.text.as_deref()) {
(ElementState::Pressed, key @ (Key::Backspace | Key::Delete), _) => {
editor.action(
context.kludgine.font_system(),
match key {
Key::Backspace => Action::Backspace,
Key::Delete => Action::Delete,
_ => unreachable!("previously matched"),
},
);
(true, HANDLED)
}
(ElementState::Pressed, key @ (Key::ArrowLeft | Key::ArrowDown | Key::ArrowUp | Key::ArrowRight), _) => {
let modifiers = context.modifiers();
match (editor.select_opt(), modifiers.state().shift_key()) {
(None, true) => {
editor.set_select_opt(Some(editor.cursor()));
}
(Some(_), false) => {
editor.set_select_opt(None);
}
_ => {}
};
editor.action(
context.kludgine.font_system(),
match key {
Key::ArrowLeft if modifiers.word_select() => Action::PreviousWord,
Key::ArrowLeft => Action::Left,
Key::ArrowDown => Action::Down,
Key::ArrowUp => Action::Up,
Key::ArrowRight if modifiers.word_select() => Action::NextWord,
Key::ArrowRight => Action::Right,
_ => unreachable!("previously matched"),
},
);
(false, HANDLED)
}
(state, _, Some("a")) if context.modifiers().primary() => {
if state.is_pressed() {
self.select_all();
}
(false, HANDLED)
}
(state, _, Some(text))
if !context.modifiers().primary()
&& text != "\t" // tab
&& text != "\r" // enter/return
&& text != "\u{1b}" // escape
=>
{
if state.is_pressed() {
editor.insert_string(text, None);
}
(state.is_pressed(), HANDLED)
}
(_, _, _) => (false, IGNORED),
};
let (text_changed, handled) = self.handle_key(input, context);
if handled.is_break() {
context.set_needs_redraw();

View file

@ -6,10 +6,11 @@ use std::ops::{Deref, DerefMut, Not};
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::path::Path;
use std::string::ToString;
use std::sync::OnceLock;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use ahash::AHashMap;
use alot::LotId;
use arboard::Clipboard;
use kludgine::app::winit::dpi::{PhysicalPosition, PhysicalSize};
use kludgine::app::winit::event::{
DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
@ -32,7 +33,7 @@ use crate::context::{
use crate::graphics::Graphics;
use crate::styles::ThemePair;
use crate::tree::Tree;
use crate::utils::ModifiersExt;
use crate::utils::{IgnorePoison, ModifiersExt};
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
use crate::widget::{
EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
@ -44,6 +45,7 @@ use crate::{initialize_tracing, ConstraintLimit, Run};
/// A currently running Gooey window.
pub struct RunningWindow<'window> {
window: kludgine::app::Window<'window, WindowCommand>,
clipboard: Option<Arc<Mutex<Clipboard>>>,
focused: Dynamic<bool>,
occluded: Dynamic<bool>,
}
@ -51,11 +53,13 @@ pub struct RunningWindow<'window> {
impl<'window> RunningWindow<'window> {
pub(crate) fn new(
window: kludgine::app::Window<'window, WindowCommand>,
clipboard: &Option<Arc<Mutex<Clipboard>>>,
focused: &Dynamic<bool>,
occluded: &Dynamic<bool>,
) -> Self {
Self {
window,
clipboard: clipboard.clone(),
focused: focused.clone(),
occluded: occluded.clone(),
}
@ -74,6 +78,15 @@ impl<'window> RunningWindow<'window> {
pub fn occluded(&self) -> &Dynamic<bool> {
&self.occluded
}
/// Returns a locked mutex guard to the OS's clipboard, if one was able to be
/// initialized when the window opened.
#[must_use]
pub fn clipboard_guard(&mut self) -> Option<MutexGuard<'_, Clipboard>> {
self.clipboard
.as_ref()
.map(|mutex| mutex.lock().ignore_poison())
}
}
impl<'window> Deref for RunningWindow<'window> {
@ -291,16 +304,21 @@ struct GooeyWindow<T> {
current_theme: ThemePair,
theme_mode: Value<ThemeMode>,
transparent: bool,
clipboard: Option<Arc<Mutex<Clipboard>>>,
}
impl<T> GooeyWindow<T>
where
T: WindowBehavior,
{
fn request_close(&mut self, window: &mut RunningWindow<'_>) -> bool {
self.should_close |= self.behavior.close_requested(window);
fn request_close(
should_close: &mut bool,
behavior: &mut T,
window: &mut RunningWindow<'_>,
) -> bool {
*should_close |= behavior.close_requested(window);
self.should_close
*should_close
}
fn keyboard_activate_widget(
@ -453,6 +471,10 @@ where
.take()
.expect("theme always present");
let clipboard = Clipboard::new()
.ok()
.map(|clipboard| Arc::new(Mutex::new(clipboard)));
let theme_mode = match context.settings.borrow_mut().theme_mode.take() {
Some(Value::Dynamic(dynamic)) => {
dynamic.update(window.theme().into());
@ -463,7 +485,7 @@ where
};
let transparent = context.settings.borrow().transparent;
let mut behavior = T::initialize(
&mut RunningWindow::new(window, &focused, &occluded),
&mut RunningWindow::new(window, &clipboard, &focused, &occluded),
context.user,
);
let root = Tree::default().push_boxed(behavior.make_root(), None);
@ -494,6 +516,7 @@ where
theme,
theme_mode,
transparent,
clipboard,
}
}
@ -521,7 +544,7 @@ where
let is_expanded = self.constrain_window_resizing(resizable, &window, graphics);
let graphics = self.contents.new_frame(graphics);
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut context = GraphicsContext {
widget: WidgetContext::new(
self.root.clone(),
@ -633,11 +656,11 @@ where
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) -> bool {
self.request_close(&mut RunningWindow::new(
window,
&self.focused,
&self.occluded,
))
Self::request_close(
&mut self.should_close,
&mut self.behavior,
&mut RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded),
)
}
// fn power_preference() -> wgpu::PowerPreference {
@ -700,7 +723,7 @@ where
let Some(target) = self.root.tree.widget_from_node(target) else {
return;
};
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut target = EventContext::new(
WidgetContext::new(
target,
@ -722,7 +745,13 @@ where
if !handled {
match input.logical_key {
Key::Character(ch) if ch == "w" && window.modifiers().primary() => {
if input.state.is_pressed() && self.request_close(&mut window) {
if input.state.is_pressed()
&& Self::request_close(
&mut self.should_close,
&mut self.behavior,
&mut window,
)
{
window.set_needs_redraw();
}
}
@ -803,7 +832,7 @@ where
.expect("missing widget")
});
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut widget = EventContext::new(
WidgetContext::new(
widget,
@ -839,7 +868,7 @@ where
.widget(self.root.id())
.expect("missing widget")
});
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut target = EventContext::new(
WidgetContext::new(
widget,
@ -866,7 +895,7 @@ where
let location = Point::<Px>::from(position);
self.cursor.location = Some(location);
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
EventContext::new(
WidgetContext::new(
@ -913,7 +942,8 @@ where
_device_id: DeviceId,
) {
if self.cursor.widget.take().is_some() {
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window =
RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut context = EventContext::new(
WidgetContext::new(
self.root.clone(),
@ -937,7 +967,7 @@ where
state: ElementState,
button: MouseButton,
) {
let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
match state {
ElementState::Pressed => {
EventContext::new(