Compare commits
102 commits
master
...
yoda-wayla
| Author | SHA1 | Date | |
|---|---|---|---|
| f8ac890b9c | |||
|
|
a31e6c6f83 | ||
|
|
2157f9d159 | ||
| 24da1dc0e4 | |||
|
|
b2419520fe | ||
|
|
836bc1e9d0 | ||
|
|
0968a10fc1 | ||
|
|
6f1b3a7170 | ||
|
|
5716b9a691 | ||
|
|
5e2b580791 | ||
|
|
021cb93fa3 | ||
|
|
ffc3ac317e | ||
|
|
85d4e4d5c2 | ||
|
|
890a53fef1 | ||
|
|
5fc4372d87 | ||
|
|
87e1787fbb | ||
|
|
9ef36c9604 | ||
|
|
2e12eba5cd | ||
|
|
880f2c7565 | ||
|
|
d0f221c48b | ||
| d12bb8e056 | |||
|
|
ffe1f1dbe3 | ||
|
|
511180679b | ||
|
|
cc5ca1ce29 | ||
|
|
39d3c99d13 | ||
|
|
aca94cf2af | ||
|
|
65e9faa43c | ||
| e891f34b32 | |||
|
|
87c0358985 | ||
| c34af04431 | |||
| d24bcd0272 | |||
|
|
7918b2826e | ||
|
|
f5aa5a6f8c | ||
|
|
b87e660eed | ||
|
|
86b236f2e1 | ||
|
|
f86d983896 | ||
|
|
65b5dbc671 | ||
|
|
5a6cd771d1 | ||
|
|
dc0e80edd8 | ||
|
|
ba4f09b7bf | ||
|
|
3a82d48548 | ||
|
|
300fa97ee2 | ||
|
|
a7c71f8dd3 | ||
|
|
b3b0ab1cd5 | ||
|
|
f6daee14ac | ||
|
|
c195232875 | ||
|
|
775abd45d1 | ||
|
|
5da1188557 | ||
|
|
5671e49c9b | ||
|
|
371dd94a2a | ||
|
|
b5b2b2fcc3 | ||
| 1a57634add | |||
| 362b4abb15 | |||
| cf7828fb70 | |||
| 3982e04aa2 | |||
| 4e2e58e03d | |||
|
|
b90b1413a6 | ||
|
|
ce5ea665e2 | ||
| f1f42f2bff | |||
| 702b6359a5 | |||
| a50a29b390 | |||
|
|
c781ff6199 | ||
|
|
633ed14411 | ||
|
|
da9d15b2e8 | ||
|
|
8e94441efa | ||
|
|
46781102fe | ||
|
|
5cc9f8d621 | ||
|
|
6be49cb530 | ||
|
|
b8b0804140 | ||
|
|
6521f7fa20 | ||
|
|
f6fa506f6c | ||
|
|
e0c7d353aa | ||
|
|
a63d57261f | ||
|
|
1bcba7e30c | ||
|
|
9f41055861 | ||
|
|
7afd9ee0f8 | ||
|
|
62b90179a2 | ||
|
|
c3c8fcf52c | ||
|
|
f725961c87 | ||
|
|
dcf4cf0194 | ||
|
|
5013f63956 | ||
|
|
ebb3b125bf | ||
|
|
590e093086 | ||
|
|
9c748f1656 | ||
|
|
dfcd989947 | ||
|
|
0094b347d4 | ||
|
|
a7d71d9ace | ||
|
|
6105d8bdef | ||
|
|
98fb6a0fba | ||
|
|
b007caba44 | ||
|
|
a7ca336eb4 | ||
|
|
2e1e4bd6e0 | ||
|
|
9ae155b8e2 | ||
|
|
346faf2e79 | ||
|
|
251254ad51 | ||
|
|
347d91f7ea | ||
|
|
1444d5c43e | ||
|
|
f752bba18f | ||
|
|
d7595fab95 | ||
|
|
e000c71ff2 | ||
|
|
3928e0d385 | ||
|
|
90d2c418a7 |
125 changed files with 4693 additions and 2415 deletions
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,3 +1,10 @@
|
|||
The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this!
|
||||
Replace this text with a description of your changes, and why these changes are important. Read and acknowledge the list below. PRs without a completed checkbox will be closed.
|
||||
|
||||
Read the contributing guidelines for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md
|
||||
---
|
||||
|
||||
- I have not included any LLM (also known as AI) generated content in this PR, including code, comments, and descriptions.
|
||||
- I understand these changes in full and will be able to respond to review comments.
|
||||
- My change is accurately described in the commit message.
|
||||
- My contribution is tested and working as described.
|
||||
- I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||
- [ ] I have read and followed all of the above requirements. For more information, see our [contributor guide](https://github.com/pop-os/pop/blob/master/CONTRIBUTING.md).
|
||||
|
|
|
|||
14
.github/workflows/require-checklist.yml
vendored
Normal file
14
.github/workflows/require-checklist.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# From https://github.com/mheap/require-checklist-action, license MIT
|
||||
name: Require Checklist
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/require-checklist-action@v2
|
||||
with:
|
||||
requireChecklist: true
|
||||
2312
Cargo.lock
generated
2312
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
74
Cargo.toml
74
Cargo.toml
|
|
@ -118,13 +118,17 @@ winit = [
|
|||
"iced_accessibility?/accesskit_winit",
|
||||
"iced_program/winit",
|
||||
]
|
||||
wayland = ["iced_renderer/wayland", "iced_winit/wayland"]
|
||||
cctk = ["iced_winit/cctk", "iced_widget/cctk", "iced_core/cctk", "wayland"]
|
||||
# Enables the sctk shell.
|
||||
wayland = [
|
||||
"iced_renderer/wayland",
|
||||
"iced_winit/wayland",
|
||||
"iced_runtime/wayland",
|
||||
"iced_widget/wayland",
|
||||
"iced_core/wayland",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
iced_devtools.workspace = true
|
||||
iced_devtools.optional = true
|
||||
iced_debug.workspace = true
|
||||
|
||||
iced_program.workspace = true
|
||||
|
|
@ -134,10 +138,9 @@ iced_renderer.workspace = true
|
|||
iced_runtime.workspace = true
|
||||
iced_widget.workspace = true
|
||||
iced_winit.workspace = true
|
||||
iced_devtools.optional = true
|
||||
iced_winit.optional = true
|
||||
iced_tester.workspace = true
|
||||
iced_tester.optional = true
|
||||
iced_winit.optional = true
|
||||
iced_highlighter.workspace = true
|
||||
iced_highlighter.optional = true
|
||||
iced_accessibility.workspace = true
|
||||
|
|
@ -190,6 +193,7 @@ members = [
|
|||
"winit",
|
||||
"examples/*",
|
||||
"accessibility",
|
||||
"build_helpers",
|
||||
]
|
||||
exclude = ["examples/integration"]
|
||||
|
||||
|
|
@ -202,7 +206,7 @@ repository = "https://github.com/iced-rs/iced"
|
|||
homepage = "https://iced.rs"
|
||||
categories = ["gui"]
|
||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||
rust-version = "1.89"
|
||||
rust-version = "1.92"
|
||||
|
||||
[workspace.dependencies]
|
||||
iced = { version = "0.14.0", path = "." }
|
||||
|
|
@ -238,10 +242,9 @@ async-std = "1.0"
|
|||
bitflags = "2.5"
|
||||
bytemuck = { version = "1.0", features = ["derive"] }
|
||||
bytes = "1.6"
|
||||
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git" }
|
||||
# cosmic-text = "0.10"
|
||||
cosmic-text = { path = "../../cosmic-text" }
|
||||
cryoglyph = { package = "cryoglyph", path = "../../glyphon" }
|
||||
dark-light = "1.0"
|
||||
cryoglyph = { package = "cryoglyph", git = "https://github.com/pop-os/glyphon.git", tag = "cosmic-0.14" }
|
||||
resvg = "0.45"
|
||||
web-sys = "0.3.69"
|
||||
guillotiere = "0.6"
|
||||
|
|
@ -275,8 +278,11 @@ tiny-skia = { version = "0.11", default-features = false, features = [
|
|||
"std",
|
||||
"simd",
|
||||
] }
|
||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086" }
|
||||
softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" }
|
||||
cctk = { path = "../../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit" }
|
||||
# Yoda: default-features=false drops softbuffer's x11 / x11-dlopen (pulled
|
||||
# tiny-xlib + as-raw-xcb-connection). The wayland + wayland-dlopen features
|
||||
# are re-activated via iced_tiny_skia's own `wayland` feature propagation.
|
||||
softbuffer = { path = "../../softbuffer", default-features = false }
|
||||
syntect = "5.2"
|
||||
tokio = "1.0"
|
||||
tracing = "0.1"
|
||||
|
|
@ -288,7 +294,7 @@ url = "2.5"
|
|||
wasm-bindgen-futures = "0.4"
|
||||
wasmtimer = "0.4.2"
|
||||
web-time = "1.1"
|
||||
wgpu = { version = "27.0", default-features = false, features = [
|
||||
wgpu = { version = "28.0", default-features = false, features = [
|
||||
"std",
|
||||
"wgsl",
|
||||
] }
|
||||
|
|
@ -302,13 +308,40 @@ wayland-client = { version = "0.31.5" }
|
|||
winapi = "0.3"
|
||||
# window_clipboard = "0.4.1"
|
||||
|
||||
window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
||||
dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
||||
mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
||||
winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
|
||||
winit-core = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
|
||||
# Yoda: window_clipboard pinned to our public fork that has x11 gated behind
|
||||
# an opt-in feature. default-features=false + features=["wayland"] drops
|
||||
# clipboard_x11 + x11rb from the build. Branch yoda-x11-optional on
|
||||
# https://forge.aditua.com/leyoda/window_clipboard.
|
||||
window_clipboard = { path = "../../window_clipboard", default-features = false, features = ["wayland"] }
|
||||
dnd = { path = "../../window_clipboard/dnd" }
|
||||
mime = { path = "../../window_clipboard/mime" }
|
||||
# Yoda: default-features = false drops winit's x11 default, which otherwise
|
||||
# pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only
|
||||
# fork doesn't need any of that — the wayland + wayland-dlopen features are
|
||||
# re-enabled via iced_winit's own feature propagation from libcosmic-yoda.
|
||||
winit = { path = "../../winit/winit", default-features = false }
|
||||
winit-core = { path = "../../winit/winit-core" }
|
||||
cursor-icon = "1.1.0"
|
||||
|
||||
[patch.'https://github.com/pop-os/softbuffer']
|
||||
softbuffer = { path = "../../softbuffer" }
|
||||
|
||||
[patch.'https://github.com/pop-os/smithay-clipboard']
|
||||
smithay-clipboard = { path = "../../smithay-clipboard" }
|
||||
|
||||
[patch.'https://github.com/pop-os/winit.git']
|
||||
dpi = { path = "../../winit/dpi" }
|
||||
winit = { path = "../../winit/winit" }
|
||||
winit-android = { path = "../../winit/winit-android" }
|
||||
winit-appkit = { path = "../../winit/winit-appkit" }
|
||||
winit-common = { path = "../../winit/winit-common" }
|
||||
winit-core = { path = "../../winit/winit-core" }
|
||||
winit-orbital = { path = "../../winit/winit-orbital" }
|
||||
winit-uikit = { path = "../../winit/winit-uikit" }
|
||||
winit-wayland = { path = "../../winit/winit-wayland" }
|
||||
winit-web = { path = "../../winit/winit-web" }
|
||||
winit-win32 = { path = "../../winit/winit-win32" }
|
||||
winit-x11 = { path = "../../winit/winit-x11" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = { level = "deny", priority = -1 }
|
||||
|
|
@ -334,3 +367,6 @@ useless_conversion = "deny"
|
|||
|
||||
[workspace.lints.rustdoc]
|
||||
broken_intra_doc_links = "forbid"
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "./build_helpers" }
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ async-io = ["accesskit_winit?/async-io"]
|
|||
tokio = ["accesskit_winit?/tokio"]
|
||||
|
||||
[dependencies]
|
||||
accesskit = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14" }
|
||||
accesskit_windows = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true }
|
||||
accesskit_macos = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true }
|
||||
accesskit_winit = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true, default-features = false, features = [
|
||||
accesskit = { git = "https://github.com/wash2/accesskit", rev = "6c20249" }
|
||||
accesskit_windows = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true }
|
||||
accesskit_macos = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true }
|
||||
accesskit_winit = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true, default-features = false, features = [
|
||||
"rwh_06",
|
||||
] }
|
||||
# accesskit = { path = "../../../accesskit/common" }
|
||||
|
|
|
|||
3
build.rs
Normal file
3
build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
17
build_helpers/Cargo.toml
Normal file
17
build_helpers/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "build_helpers"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
categories.workspace = true
|
||||
keywords.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cfg_aliases = { version = "0.2.1" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
24
build_helpers/src/lib.rs
Normal file
24
build_helpers/src/lib.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
pub fn cfg_aliases_setup() {
|
||||
// The script doesn't depend on our code
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Setup cfg aliases
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
|
||||
macos_platform: { target_os = "macos" },
|
||||
ios_platform: { target_os = "ios" },
|
||||
windows_platform: { target_os = "windows" },
|
||||
apple: { any(target_os = "ios", target_os = "macos") },
|
||||
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
|
||||
redox: { target_os = "redox" },
|
||||
|
||||
// Native displays.
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
|
||||
xdg_portal: { all(feature = "xdg-portal", free_unix, not(wasm), not(redox)) },
|
||||
orbital_platform: { redox },
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ crisp = []
|
|||
basic-shaping = []
|
||||
advanced-shaping = []
|
||||
a11y = ["iced_accessibility"]
|
||||
wayland = ["cctk"]
|
||||
|
||||
[dependencies]
|
||||
palette = "0.7"
|
||||
|
|
@ -31,6 +32,7 @@ num-traits.workspace = true
|
|||
rustc-hash.workspace = true
|
||||
smol_str.workspace = true
|
||||
thiserror.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
web-time.workspace = true
|
||||
|
||||
serde.workspace = true
|
||||
|
|
@ -42,8 +44,6 @@ window_clipboard.workspace = true
|
|||
dnd.workspace = true
|
||||
mime.workspace = true
|
||||
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
# /TODO(POP)
|
||||
[dependencies.iced_accessibility]
|
||||
version = "0.1.0"
|
||||
|
|
@ -55,3 +55,10 @@ raw-window-handle.workspace = true
|
|||
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "../build_helpers" }
|
||||
|
|
|
|||
3
core/build.rs
Normal file
3
core/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::event::{self, Event};
|
||||
use crate::event::Event;
|
||||
use crate::id::Id;
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ use crate::keyboard;
|
|||
use crate::mouse;
|
||||
use crate::touch;
|
||||
use crate::window;
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
/// A platform specific event for wayland
|
||||
pub mod wayland;
|
||||
/// A user interface event.
|
||||
|
|
@ -49,7 +50,7 @@ pub enum Event {
|
|||
/// A platform specific event
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PlatformSpecific {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
/// A Wayland specific event
|
||||
Wayland(wayland::Event),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,4 +46,6 @@ pub enum Event {
|
|||
Subsurface(SubsurfaceEvent),
|
||||
/// Keyboard inhibit shortcuts
|
||||
ShortcutsInhibited(bool),
|
||||
/// Blur Support Enabled
|
||||
BlurEnabled,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
|
||||
use crate::{Clipboard, Event, Layout, Overlay, Shell, Size};
|
||||
|
||||
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||
/// children.
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ pub enum Action {
|
|||
SelectLine,
|
||||
/// Select the entire buffer.
|
||||
SelectAll,
|
||||
/// Clear the selection
|
||||
ClearSelection,
|
||||
/// Perform an [`Edit`].
|
||||
Edit(Edit),
|
||||
/// Click the [`Editor`] at the given [`Point`].
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub use scrollable::Scrollable;
|
|||
pub use text_input::TextInput;
|
||||
|
||||
use crate::widget::Id;
|
||||
use crate::{Rectangle, Vector};
|
||||
use crate::{Rectangle, Vector, window};
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
|
|
@ -75,6 +75,12 @@ pub trait Operation<T = ()>: Send {
|
|||
fn finish(&self) -> Outcome<T> {
|
||||
Outcome::None
|
||||
}
|
||||
|
||||
/// Track the active window id being processed if relevant to the operation
|
||||
fn set_window_id(&mut self, _id: window::Id) {}
|
||||
|
||||
/// Used to mark the beginning of operations for a widget and its children
|
||||
fn pre_operation(&mut self, _id: Option<&Id>) {}
|
||||
}
|
||||
|
||||
impl<T, O> Operation<O> for Box<T>
|
||||
|
|
@ -140,6 +146,14 @@ where
|
|||
fn finish(&self) -> Outcome<O> {
|
||||
self.as_ref().finish()
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.as_mut().set_window_id(id);
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.as_mut().pre_operation(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of an [`Operation`].
|
||||
|
|
@ -243,6 +257,14 @@ where
|
|||
fn finish(&self) -> Outcome<O> {
|
||||
Outcome::None
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.operation.pre_operation(id);
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.operation.set_window_id(id);
|
||||
}
|
||||
}
|
||||
|
||||
BlackBox { operation }
|
||||
|
|
@ -341,6 +363,10 @@ where
|
|||
) {
|
||||
self.operation.custom(id, bounds, state);
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.operation.pre_operation(id);
|
||||
}
|
||||
}
|
||||
|
||||
self.operation.traverse(&mut |operation| {
|
||||
|
|
@ -410,6 +436,14 @@ where
|
|||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.operation.set_window_id(id);
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.operation.pre_operation(id);
|
||||
}
|
||||
}
|
||||
|
||||
Map {
|
||||
|
|
@ -515,6 +549,14 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.operation.set_window_id(id);
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.operation.pre_operation(id);
|
||||
}
|
||||
}
|
||||
|
||||
Chain {
|
||||
|
|
@ -566,6 +608,14 @@ pub fn scoped<T: 'static>(
|
|||
outcome => outcome,
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.operation.pre_operation(id);
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.operation.set_window_id(id);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedOperation {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::Operation;
|
|||
use crate::{
|
||||
Rectangle,
|
||||
id::Id,
|
||||
widget::operation::{Outcome, focusable::Count},
|
||||
widget::operation::Outcome,
|
||||
};
|
||||
|
||||
/// Produces an [`Operation`] that searches for the Id
|
||||
|
|
|
|||
|
|
@ -22,16 +22,18 @@
|
|||
//! ```
|
||||
use crate::alignment;
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::mouse::{self, click};
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::text::paragraph::{self, Paragraph};
|
||||
use crate::text::{self, Fragment};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget,
|
||||
Clipboard, Color, Element, Event, Layout, Length, Pixels, Point, Rectangle,
|
||||
Shell, Size, Theme, Widget, keyboard, touch,
|
||||
};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub use text::{Alignment, Ellipsize, LineHeight, Shaping, Wrapping};
|
||||
|
||||
/// A bunch of text.
|
||||
|
|
@ -65,6 +67,7 @@ where
|
|||
fragment: text::Fragment<'a>,
|
||||
format: Format<Renderer::Font>,
|
||||
class: Theme::Class<'a>,
|
||||
selectable: bool,
|
||||
}
|
||||
|
||||
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
|
||||
|
|
@ -79,6 +82,7 @@ where
|
|||
fragment: fragment.into_fragment(),
|
||||
format: Format::default(),
|
||||
class: Theme::default(),
|
||||
selectable: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +193,17 @@ where
|
|||
{
|
||||
let color = color.map(Into::into);
|
||||
|
||||
self.style(move |_theme| Style { color })
|
||||
self.style(move |_theme| Style {
|
||||
color,
|
||||
..Style::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Makes the [`Text`] selectable. When enabled, the user can click and
|
||||
/// drag to select text, and copy it with Ctrl+C / Cmd+C.
|
||||
pub fn selectable(mut self) -> Self {
|
||||
self.selectable = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style class of the [`Text`].
|
||||
|
|
@ -202,7 +216,116 @@ where
|
|||
}
|
||||
|
||||
/// The internal state of a [`Text`] widget.
|
||||
pub type State<P> = paragraph::Plain<P>;
|
||||
pub struct State<P: Paragraph> {
|
||||
/// The cached paragraph layout.
|
||||
pub paragraph: paragraph::Plain<P>,
|
||||
/// Lazily allocated when text is selectable and first interacted with.
|
||||
selection: Option<Box<SelectionState>>,
|
||||
focused: bool,
|
||||
keyboard_focused: bool,
|
||||
context_menu_position: Option<Point>,
|
||||
clipboard_has_text: bool,
|
||||
}
|
||||
|
||||
impl<P: Paragraph> Default for State<P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paragraph: paragraph::Plain::default(),
|
||||
selection: None,
|
||||
focused: false,
|
||||
keyboard_focused: false,
|
||||
context_menu_position: None,
|
||||
clipboard_has_text: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Paragraph> std::fmt::Debug for State<P> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("State")
|
||||
.field("selection", &self.selection)
|
||||
.field("focused", &self.focused)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Paragraph> State<P> {
|
||||
/// Returns `true` if the widget currently has focus.
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
/// Returns `true` if focus was gained via keyboard navigation (Tab).
|
||||
pub fn is_keyboard_focused(&self) -> bool {
|
||||
self.keyboard_focused
|
||||
}
|
||||
|
||||
/// Clears focus, selection, and all interaction state.
|
||||
pub fn clear_focus(&mut self) {
|
||||
self.focused = false;
|
||||
self.keyboard_focused = false;
|
||||
self.context_menu_position = None;
|
||||
if let Some(sel) = &mut self.selection {
|
||||
sel.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the context menu position, if a context menu should be shown.
|
||||
pub fn context_menu_position(&self) -> Option<Point> {
|
||||
self.context_menu_position
|
||||
}
|
||||
|
||||
/// Sets or clears the context menu position.
|
||||
pub fn set_context_menu_position(&mut self, pos: Option<Point>) {
|
||||
self.context_menu_position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Paragraph> std::ops::Deref for State<P> {
|
||||
type Target = paragraph::Plain<P>;
|
||||
|
||||
fn deref(&self) -> ¶graph::Plain<P> {
|
||||
&self.paragraph
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Paragraph> std::ops::DerefMut for State<P> {
|
||||
fn deref_mut(&mut self) -> &mut paragraph::Plain<P> {
|
||||
&mut self.paragraph
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct SelectionState {
|
||||
anchor: usize,
|
||||
end: usize,
|
||||
dragging: bool,
|
||||
modifiers: keyboard::Modifiers,
|
||||
last_click: Option<click::Click>,
|
||||
}
|
||||
|
||||
impl SelectionState {
|
||||
fn clear(&mut self) {
|
||||
self.anchor = 0;
|
||||
self.end = 0;
|
||||
self.dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Paragraph> crate::widget::operation::Focusable for State<P> {
|
||||
fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
fn focus(&mut self) {
|
||||
self.focused = true;
|
||||
self.keyboard_focused = true;
|
||||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
self.clear_focus();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Text<'_, Theme, Renderer>
|
||||
|
|
@ -215,7 +338,7 @@ where
|
|||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(paragraph::Plain::<Renderer::Paragraph>::default())
|
||||
tree::State::new(State::<Renderer::Paragraph>::default())
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
|
|
@ -231,8 +354,10 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
|
||||
layout(
|
||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
||||
&mut state.paragraph,
|
||||
renderer,
|
||||
limits,
|
||||
&self.fragment,
|
||||
|
|
@ -252,25 +377,372 @@ where
|
|||
) {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
let style = theme.style(&self.class);
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
state.raw(),
|
||||
style,
|
||||
viewport,
|
||||
let bounds = layout.bounds();
|
||||
let paragraph = state.paragraph.raw();
|
||||
let anchor = bounds.anchor(
|
||||
paragraph.min_bounds(),
|
||||
paragraph.align_x(),
|
||||
paragraph.align_y(),
|
||||
);
|
||||
|
||||
let rects: Vec<Rectangle> = state
|
||||
.selection
|
||||
.as_ref()
|
||||
.filter(|sel| sel.anchor != sel.end)
|
||||
.map(|sel| {
|
||||
let content: &str = self.fragment.as_ref();
|
||||
let lo_byte =
|
||||
grapheme_to_byte(content, sel.anchor.min(sel.end));
|
||||
let hi_byte =
|
||||
grapheme_to_byte(content, sel.anchor.max(sel.end));
|
||||
|
||||
paragraph
|
||||
.highlight(
|
||||
0,
|
||||
(lo_byte, text::Affinity::After),
|
||||
(hi_byte, text::Affinity::Before),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|r| Rectangle {
|
||||
x: anchor.x + r.x,
|
||||
y: anchor.y + r.y,
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let fill_selection = |renderer: &mut Renderer| {
|
||||
for r in &rects {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: *r,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.selected_fill,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if style.selected_text_color.is_none() {
|
||||
fill_selection(renderer);
|
||||
}
|
||||
draw(renderer, defaults, bounds, paragraph, style, viewport);
|
||||
if let Some(color) = style.selected_text_color {
|
||||
fill_selection(renderer);
|
||||
for r in &rects {
|
||||
renderer.with_layer(*r, |renderer| {
|
||||
renderer
|
||||
.fill_paragraph(paragraph, anchor, color, *viewport);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
if !self.selectable {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
let bounds = layout.bounds();
|
||||
let content: &str = self.fragment.as_ref();
|
||||
let grapheme_count = content.graphemes(true).count();
|
||||
let paragraph = state.paragraph.raw();
|
||||
|
||||
// Any click outside this widget clears selection and focus.
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(_))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. })
|
||||
) {
|
||||
if cursor.position_over(bounds).is_none() {
|
||||
let was_visible = state.focused
|
||||
|| state.keyboard_focused
|
||||
|| state
|
||||
.selection
|
||||
.as_ref()
|
||||
.is_some_and(|sel| sel.anchor != sel.end);
|
||||
|
||||
state.focused = false;
|
||||
state.keyboard_focused = false;
|
||||
if let Some(sel) = &mut state.selection {
|
||||
sel.clear();
|
||||
}
|
||||
|
||||
if was_visible {
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
if state.context_menu_position.take().is_some() {
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(pos) = cursor.position_over(bounds) {
|
||||
let sel = state.selection.get_or_insert_with(|| {
|
||||
Box::new(SelectionState::default())
|
||||
});
|
||||
|
||||
let anchor = bounds.anchor(
|
||||
paragraph.min_bounds(),
|
||||
paragraph.align_x(),
|
||||
paragraph.align_y(),
|
||||
);
|
||||
let relative =
|
||||
Point::new(pos.x - anchor.x, pos.y - anchor.y);
|
||||
|
||||
let grapheme_pos =
|
||||
hit_to_grapheme(paragraph, relative, content);
|
||||
|
||||
let new_click = click::Click::new(
|
||||
pos,
|
||||
mouse::Button::Left,
|
||||
sel.last_click.take(),
|
||||
);
|
||||
|
||||
match new_click.kind() {
|
||||
click::Kind::Single => {
|
||||
if sel.modifiers.shift() {
|
||||
sel.end = grapheme_pos;
|
||||
} else {
|
||||
sel.anchor = grapheme_pos;
|
||||
sel.end = grapheme_pos;
|
||||
}
|
||||
sel.dragging = true;
|
||||
}
|
||||
click::Kind::Double => {
|
||||
sel.anchor =
|
||||
previous_start_of_word(content, grapheme_pos);
|
||||
sel.end = next_end_of_word(content, grapheme_pos);
|
||||
sel.dragging = true;
|
||||
}
|
||||
click::Kind::Triple => {
|
||||
sel.anchor = 0;
|
||||
sel.end = grapheme_count;
|
||||
sel.dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
sel.last_click = Some(new_click);
|
||||
state.focused = true;
|
||||
state.keyboard_focused = false;
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||
if let Some(pos) = cursor.position_over(bounds) {
|
||||
state.context_menu_position = Some(pos);
|
||||
state.clipboard_has_text = clipboard_has_text(clipboard);
|
||||
state.focused = true;
|
||||
state.keyboard_focused = false;
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||
if let Some(sel) = &mut state.selection {
|
||||
sel.dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
Event::Mouse(mouse::Event::CursorMoved { position })
|
||||
| Event::Touch(touch::Event::FingerMoved { position, .. }) => {
|
||||
if let Some(sel) = &mut state.selection {
|
||||
if sel.dragging {
|
||||
let anchor = bounds.anchor(
|
||||
paragraph.min_bounds(),
|
||||
paragraph.align_x(),
|
||||
paragraph.align_y(),
|
||||
);
|
||||
let relative = Point::new(
|
||||
position.x - anchor.x,
|
||||
position.y - anchor.y,
|
||||
);
|
||||
|
||||
let end = hit_to_grapheme(paragraph, relative, content);
|
||||
|
||||
// Only the drag reaching a new grapheme changes what
|
||||
// is drawn; plain motion inside one does not.
|
||||
if end != sel.end {
|
||||
sel.end = end;
|
||||
shell.request_redraw();
|
||||
}
|
||||
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key,
|
||||
modifiers,
|
||||
physical_key,
|
||||
text: _,
|
||||
..
|
||||
}) => {
|
||||
if !state.focused {
|
||||
return;
|
||||
}
|
||||
let sel = state
|
||||
.selection
|
||||
.get_or_insert_with(|| Box::new(SelectionState::default()));
|
||||
|
||||
if modifiers.command() {
|
||||
match key.to_latin(*physical_key) {
|
||||
Some('c') => {
|
||||
let left = sel.anchor.min(sel.end);
|
||||
let right = sel.anchor.max(sel.end);
|
||||
if left != right {
|
||||
let selected: String = content
|
||||
.graphemes(true)
|
||||
.skip(left)
|
||||
.take(right - left)
|
||||
.collect();
|
||||
clipboard.write(
|
||||
crate::clipboard::Kind::Standard,
|
||||
selected,
|
||||
);
|
||||
}
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Some('a') => {
|
||||
sel.anchor = 0;
|
||||
sel.end = grapheme_count;
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match key {
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
|
||||
let by_word = is_jump_modifier(*modifiers);
|
||||
if modifiers.shift() {
|
||||
sel.end = if by_word {
|
||||
previous_start_of_word(content, sel.end)
|
||||
} else {
|
||||
sel.end.saturating_sub(1)
|
||||
};
|
||||
} else {
|
||||
let left = sel.anchor.min(sel.end);
|
||||
let pos = if by_word {
|
||||
previous_start_of_word(content, left)
|
||||
} else {
|
||||
left.saturating_sub(1)
|
||||
};
|
||||
sel.anchor = pos;
|
||||
sel.end = pos;
|
||||
}
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
|
||||
let by_word = is_jump_modifier(*modifiers);
|
||||
if modifiers.shift() {
|
||||
sel.end = if by_word {
|
||||
next_end_of_word(content, sel.end)
|
||||
} else {
|
||||
(sel.end + 1).min(grapheme_count)
|
||||
};
|
||||
} else {
|
||||
let right = sel.anchor.max(sel.end);
|
||||
let pos = if by_word {
|
||||
next_end_of_word(content, right)
|
||||
} else {
|
||||
(right + 1).min(grapheme_count)
|
||||
};
|
||||
sel.anchor = pos;
|
||||
sel.end = pos;
|
||||
}
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::Home) => {
|
||||
if modifiers.shift() {
|
||||
sel.end = 0;
|
||||
} else {
|
||||
sel.anchor = 0;
|
||||
sel.end = 0;
|
||||
}
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::End) => {
|
||||
if modifiers.shift() {
|
||||
sel.end = grapheme_count;
|
||||
} else {
|
||||
sel.anchor = grapheme_count;
|
||||
sel.end = grapheme_count;
|
||||
}
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
if let Some(sel) = &mut state.selection {
|
||||
sel.modifiers = *modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if self.selectable && cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Text
|
||||
} else {
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
operation: &mut dyn super::Operation,
|
||||
) {
|
||||
operation.text(None, layout.bounds(), &self.fragment);
|
||||
|
||||
if self.selectable {
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
operation.focusable(None, layout.bounds(), state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
@ -471,12 +943,28 @@ where
|
|||
}
|
||||
|
||||
/// The appearance of some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Style {
|
||||
/// The [`Color`] of the text.
|
||||
///
|
||||
/// The default, `None`, means using the inherited color.
|
||||
pub color: Option<Color>,
|
||||
/// The fill [`Color`] of the selection highlight.
|
||||
pub selected_fill: Color,
|
||||
/// The [`Color`] of selected text.
|
||||
///
|
||||
/// The default, `None`, keeps the regular text color.
|
||||
pub selected_text_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: None,
|
||||
selected_fill: DEFAULT_SELECTION_COLOR,
|
||||
selected_text_color: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme catalog of a [`Text`].
|
||||
|
|
@ -510,13 +998,14 @@ impl Catalog for Theme {
|
|||
|
||||
/// The default text styling; color is inherited.
|
||||
pub fn default(_theme: &Theme) -> Style {
|
||||
Style { color: None }
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Text with the default base color.
|
||||
pub fn base(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().text),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,6 +1013,7 @@ pub fn base(theme: &Theme) -> Style {
|
|||
pub fn primary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().primary),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +1021,7 @@ pub fn primary(theme: &Theme) -> Style {
|
|||
pub fn secondary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.extended_palette().secondary.base.color),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -538,6 +1029,7 @@ pub fn secondary(theme: &Theme) -> Style {
|
|||
pub fn success(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().success),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -545,6 +1037,7 @@ pub fn success(theme: &Theme) -> Style {
|
|||
pub fn warning(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().warning),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,5 +1045,206 @@ pub fn warning(theme: &Theme) -> Style {
|
|||
pub fn danger(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().danger),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
use crate::widget::tree::Tree as WidgetTree;
|
||||
|
||||
/// Returns `true` if the clipboard currently holds non-empty text.
|
||||
pub fn clipboard_has_text(clipboard: &dyn Clipboard) -> bool {
|
||||
clipboard
|
||||
.read(crate::clipboard::Kind::Standard)
|
||||
.is_some_and(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Implement this on a **widget** to enable context menu support for
|
||||
/// text selection (Copy, Select All, and optionally Cut / Paste) in libcosmic
|
||||
pub trait HasSelectableText {
|
||||
/// Returns the currently selected text, if any.
|
||||
fn selected_text(&self, tree: &WidgetTree) -> Option<String>;
|
||||
|
||||
/// Selects all text.
|
||||
fn select_all(&self, tree: &mut WidgetTree);
|
||||
|
||||
/// Returns `true` if the widget is editable (shows Cut / Paste).
|
||||
fn is_editable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if the widget has any text (enables Select All).
|
||||
fn has_text(&self, _tree: &WidgetTree) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns whether the clipboard held text when the context menu was
|
||||
/// requested (enables Paste). Widgets cache this on right-click because
|
||||
/// overlays have no clipboard access.
|
||||
fn clipboard_has_text(&self, _tree: &WidgetTree) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if the widget is currently focused.
|
||||
fn is_focused(&self, tree: &WidgetTree) -> bool;
|
||||
|
||||
/// Returns the position where the context menu should appear
|
||||
fn context_menu_position(&self, tree: &WidgetTree) -> Option<Point>;
|
||||
|
||||
/// Sets or clears the context menu position.
|
||||
fn set_context_menu_position(
|
||||
&self,
|
||||
tree: &mut WidgetTree,
|
||||
pos: Option<Point>,
|
||||
);
|
||||
|
||||
/// Copies the selection to the clipboard.
|
||||
fn copy_to_clipboard(
|
||||
&self,
|
||||
tree: &WidgetTree,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
) {
|
||||
if let Some(text) = self.selected_text(tree) {
|
||||
clipboard.write(crate::clipboard::Kind::Standard, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes the selected text and returns the new full content.
|
||||
/// Only called when [`is_editable`](Self::is_editable) is `true`.
|
||||
fn delete_selection(&self, _tree: &mut WidgetTree) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Inserts `text` at the cursor (replacing any selection) and returns
|
||||
/// the new full content.
|
||||
/// Only called when [`is_editable`](Self::is_editable) is `true`.
|
||||
fn paste_text(
|
||||
&self,
|
||||
_tree: &mut WidgetTree,
|
||||
_text: &str,
|
||||
) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Theme: Catalog, Renderer: text::Renderer> HasSelectableText
|
||||
for Text<'_, Theme, Renderer>
|
||||
{
|
||||
fn selected_text(&self, tree: &WidgetTree) -> Option<String> {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
let sel = state.selection.as_ref()?;
|
||||
let left = sel.anchor.min(sel.end);
|
||||
let right = sel.anchor.max(sel.end);
|
||||
if left == right {
|
||||
return None;
|
||||
}
|
||||
let content = state.paragraph.content();
|
||||
let lo = grapheme_to_byte(content, left);
|
||||
let hi = grapheme_to_byte(content, right);
|
||||
content.get(lo..hi).map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
fn select_all(&self, tree: &mut WidgetTree) {
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
let count = state.paragraph.content().graphemes(true).count();
|
||||
let sel = state
|
||||
.selection
|
||||
.get_or_insert_with(|| Box::new(SelectionState::default()));
|
||||
sel.anchor = 0;
|
||||
sel.end = count;
|
||||
}
|
||||
|
||||
fn has_text(&self, tree: &WidgetTree) -> bool {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
!state.paragraph.content().is_empty()
|
||||
}
|
||||
|
||||
fn clipboard_has_text(&self, tree: &WidgetTree) -> bool {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
state.clipboard_has_text
|
||||
}
|
||||
|
||||
fn is_focused(&self, tree: &WidgetTree) -> bool {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
state.is_focused()
|
||||
}
|
||||
|
||||
fn context_menu_position(&self, tree: &WidgetTree) -> Option<Point> {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
state.context_menu_position()
|
||||
}
|
||||
|
||||
fn set_context_menu_position(
|
||||
&self,
|
||||
tree: &mut WidgetTree,
|
||||
pos: Option<Point>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
state.set_context_menu_position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_SELECTION_COLOR: Color = Color {
|
||||
r: 0.0,
|
||||
g: 0.47,
|
||||
b: 0.84,
|
||||
a: 0.3,
|
||||
};
|
||||
|
||||
fn grapheme_to_byte(content: &str, grapheme_index: usize) -> usize {
|
||||
content
|
||||
.graphemes(true)
|
||||
.take(grapheme_index)
|
||||
.map(|g| g.len())
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn hit_to_grapheme<P: Paragraph>(
|
||||
paragraph: &P,
|
||||
point: Point,
|
||||
content: &str,
|
||||
) -> usize {
|
||||
match paragraph.hit_test(point) {
|
||||
Some(hit) => {
|
||||
let byte_offset = hit.cursor().min(content.len());
|
||||
content[..byte_offset].graphemes(true).count()
|
||||
}
|
||||
None => content.graphemes(true).count(),
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_start_of_word(content: &str, grapheme_index: usize) -> usize {
|
||||
let graphemes: Vec<&str> = content.graphemes(true).collect();
|
||||
let clamped = grapheme_index.min(graphemes.len());
|
||||
let before: String = graphemes[..clamped].concat();
|
||||
|
||||
UnicodeSegmentation::split_word_bound_indices(&*before)
|
||||
.filter(|(_, word)| !word.trim_start().is_empty())
|
||||
.next_back()
|
||||
.map_or(0, |(i, prev_word)| {
|
||||
clamped
|
||||
- prev_word.graphemes(true).count()
|
||||
- before[i + prev_word.len()..].graphemes(true).count()
|
||||
})
|
||||
}
|
||||
|
||||
fn next_end_of_word(content: &str, grapheme_index: usize) -> usize {
|
||||
let graphemes: Vec<&str> = content.graphemes(true).collect();
|
||||
let clamped = grapheme_index.min(graphemes.len());
|
||||
let after: String = graphemes[clamped..].concat();
|
||||
|
||||
UnicodeSegmentation::split_word_bound_indices(&*after)
|
||||
.find(|(_, word)| !word.trim_start().is_empty())
|
||||
.map_or(graphemes.len(), |(i, next_word)| {
|
||||
clamped
|
||||
+ next_word.graphemes(true).count()
|
||||
+ after[..i].graphemes(true).count()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_jump_modifier(modifiers: keyboard::Modifiers) -> bool {
|
||||
if cfg!(target_os = "macos") {
|
||||
modifiers.alt()
|
||||
} else {
|
||||
modifiers.control()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use iced::widget::{button, column, text, Column};
|
||||
use iced::Center;
|
||||
use iced::widget::{Column, button, column, text};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run(Counter::update, Counter::view)
|
||||
|
|
@ -42,7 +42,7 @@ impl Counter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use iced_test::{Error, simulator};
|
||||
use iced_test::{simulator, Error};
|
||||
|
||||
#[test]
|
||||
fn it_counts() -> Result<(), Error> {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ impl Pipeline {
|
|||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ impl Pipeline {
|
|||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("cubes pipeline layout"),
|
||||
bind_group_layouts: &[&uniform_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader =
|
||||
|
|
@ -280,7 +280,7 @@ impl Pipeline {
|
|||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
@ -388,6 +388,7 @@ impl Pipeline {
|
|||
),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_viewport(
|
||||
|
|
@ -478,7 +479,7 @@ impl DepthPipeline {
|
|||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("cubes.depth_pipeline.layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader =
|
||||
|
|
@ -520,7 +521,7 @@ impl DepthPipeline {
|
|||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
@ -586,6 +587,7 @@ impl DepthPipeline {
|
|||
),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_viewport(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ impl Scene {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -64,8 +65,8 @@ fn build_pipeline(
|
|||
let pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
|
|
@ -101,7 +102,7 @@ fn build_pipeline(
|
|||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ use std::{
|
|||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use iced::{Element, id::Id};
|
||||
use iced::{
|
||||
Event, Length, Rectangle,
|
||||
clipboard::{
|
||||
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
||||
mime::AllowedMimeTypes,
|
||||
},
|
||||
event,
|
||||
id::Internal,
|
||||
mouse, overlay,
|
||||
mouse, overlay, Event, Length, Rectangle,
|
||||
};
|
||||
use iced::{id::Id, Element};
|
||||
use iced_core::{
|
||||
self, Clipboard, Layout, Shell, Widget, layout,
|
||||
widget::{Tree, tree},
|
||||
self, layout,
|
||||
widget::{tree, Tree},
|
||||
Clipboard, Layout, Shell, Widget,
|
||||
};
|
||||
|
||||
pub fn dnd_destination<'a, Message: 'static>(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use std::any::Any;
|
||||
|
||||
use iced::Element;
|
||||
use iced::id::Id;
|
||||
use iced::widget::container;
|
||||
use iced::Element;
|
||||
use iced::{
|
||||
Event, Length, Point, Rectangle,
|
||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
||||
event, mouse, overlay,
|
||||
event, mouse, overlay, Event, Length, Point, Rectangle,
|
||||
};
|
||||
use iced_core::{
|
||||
Clipboard, Shell, layout, renderer,
|
||||
widget::{Tree, tree},
|
||||
layout, renderer,
|
||||
widget::{tree, Tree},
|
||||
Clipboard, Shell,
|
||||
};
|
||||
use iced_core::{Layout, Widget};
|
||||
|
||||
|
|
@ -37,11 +37,11 @@ pub struct DndSource<'a, Message, AppMessage, D> {
|
|||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> DndSource<'a, Message, AppMessage, D>
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> DndSource<'a, Message, AppMessage, D>
|
||||
{
|
||||
pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
|
||||
Self {
|
||||
|
|
@ -119,11 +119,11 @@ impl<
|
|||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> Widget<Message, iced::Theme, iced::Renderer>
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> Widget<Message, iced::Theme, iced::Renderer>
|
||||
for DndSource<'a, Message, AppMessage, D>
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
|
|
@ -357,11 +357,11 @@ impl<
|
|||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
|
||||
{
|
||||
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
|
||||
Element::new(e)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use cctk::sctk::reexports::{
|
|||
|
||||
use iced::platform_specific::shell::commands::subsurface::get_subsurface;
|
||||
use iced::{
|
||||
Element, Length, Subscription, Task,
|
||||
event::wayland::Event as WaylandEvent,
|
||||
platform_specific::{
|
||||
runtime::wayland::subsurface::SctkSubsurfaceSettings,
|
||||
|
|
@ -15,6 +14,7 @@ use iced::{
|
|||
},
|
||||
widget::{button, column, text, text_input},
|
||||
window::{self, Id, Settings},
|
||||
Element, Length, Subscription, Task,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
|
|
|||
|
|
@ -477,6 +477,7 @@ fn delete_icon() -> Text<'static> {
|
|||
fn subtle(theme: &Theme) -> text::Style {
|
||||
text::Style {
|
||||
color: Some(theme.extended_palette().background.strongest.color),
|
||||
..text::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Load and operate on images.
|
||||
#[cfg(feature = "image")]
|
||||
use crate::core::Bytes;
|
||||
#[cfg(feature = "image")]
|
||||
use crate::core::Size;
|
||||
|
||||
use crate::core::Color;
|
||||
use crate::core::Radians;
|
||||
use crate::core::Rectangle;
|
||||
use crate::core::image;
|
||||
use crate::core::svg;
|
||||
|
|
@ -180,3 +180,105 @@ fn to_error(error: ::image::ImageError) -> image::Error {
|
|||
error => image::Error::Invalid(Arc::new(error)),
|
||||
}
|
||||
}
|
||||
|
||||
/// resample a raster image to this target
|
||||
#[cfg(feature = "image")]
|
||||
pub fn downsample_target(
|
||||
native: Size<u32>,
|
||||
bounds: Size<f32>,
|
||||
) -> Option<Size<u32>> {
|
||||
if !(bounds.width >= 1.0 && bounds.height >= 1.0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Round up to 4px so an animated resize does not make a copy per pixel
|
||||
// TODO: maybe find a better approach
|
||||
let quantize = |length: f32| (length.ceil() as u32).div_ceil(4) * 4;
|
||||
|
||||
let target = Size::new(
|
||||
quantize(bounds.width).min(native.width),
|
||||
quantize(bounds.height).min(native.height),
|
||||
);
|
||||
|
||||
let minified = native.width as f32 >= target.width as f32 * 1.25
|
||||
|| native.height as f32 >= target.height as f32 * 1.25;
|
||||
|
||||
// If the target is big then Lanczos will be too expensive, and sampler is good enough
|
||||
let small = u64::from(target.width) * u64::from(target.height) <= 1 << 18;
|
||||
|
||||
(minified && small).then_some(target)
|
||||
}
|
||||
|
||||
/// Resamples premultiplied pixels down to `target`.
|
||||
#[cfg(feature = "image")]
|
||||
pub fn downsample_premultiplied(
|
||||
pixels: &[u8],
|
||||
size: Size<u32>,
|
||||
target: Size<u32>,
|
||||
) -> Vec<u8> {
|
||||
let image =
|
||||
::image::RgbaImage::from_raw(size.width, size.height, pixels.to_vec())
|
||||
.expect("pixels hold width * height RGBA pixels");
|
||||
|
||||
resize(&image, target).into_raw()
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn resize(image: &::image::RgbaImage, target: Size<u32>) -> ::image::RgbaImage {
|
||||
use ::image::imageops::{self, FilterType};
|
||||
use std::borrow::Cow;
|
||||
|
||||
// if image is too big compared to target, box average it to double the target
|
||||
// and then do Lanczos. The final result is almost the same, but lanczos is too
|
||||
// expensive on large images.
|
||||
let image = if image.width() >= target.width * 4
|
||||
&& image.height() >= target.height * 4
|
||||
{
|
||||
Cow::Owned(imageops::thumbnail(
|
||||
image,
|
||||
target.width * 2,
|
||||
target.height * 2,
|
||||
))
|
||||
} else {
|
||||
Cow::Borrowed(image)
|
||||
};
|
||||
|
||||
imageops::resize(&*image, target.width, target.height, FilterType::Lanczos3)
|
||||
}
|
||||
|
||||
/// Resamples RGBA pixels down to `target`.
|
||||
///
|
||||
/// Premultiplies the image, to avoid fringing the edges of an icon when interpolating
|
||||
/// transparent pixels.
|
||||
#[cfg(feature = "image")]
|
||||
pub fn downsample(image: &Buffer, target: Size<u32>) -> ::image::RgbaImage {
|
||||
let mut image = ::image::RgbaImage::from_raw(
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.as_raw().to_vec(),
|
||||
)
|
||||
.expect("buffer holds width * height RGBA pixels");
|
||||
|
||||
for pixel in image.pixels_mut() {
|
||||
let alpha = u32::from(pixel[3]);
|
||||
|
||||
for channel in &mut pixel.0[..3] {
|
||||
*channel = ((u32::from(*channel) * alpha + 127) / 255) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
let mut image = resize(&image, target);
|
||||
|
||||
for pixel in image.pixels_mut() {
|
||||
let alpha = u32::from(pixel[3]);
|
||||
|
||||
if alpha > 0 {
|
||||
for channel in &mut pixel.0[..3] {
|
||||
*channel = ((u32::from(*channel) * 255 + alpha / 2) / alpha)
|
||||
.min(255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,13 +83,20 @@ impl<T: Layer> Stack<T> {
|
|||
/// Pushes a new clipping region in the [`Stack`]; creating a new layer in the
|
||||
/// process.
|
||||
pub fn push_clip(&mut self, bounds: Rectangle) {
|
||||
let bounds = bounds * self.transformation();
|
||||
let bounds = bounds
|
||||
.intersection(&self.layers[self.current].bounds())
|
||||
.unwrap_or(Rectangle {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
..bounds
|
||||
});
|
||||
|
||||
self.previous.push(self.current);
|
||||
|
||||
self.current = self.active_count;
|
||||
self.active_count += 1;
|
||||
|
||||
let bounds = bounds * self.transformation();
|
||||
|
||||
if self.current == self.layers.len() {
|
||||
self.layers.push(T::with_bounds(bounds));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -161,6 +161,14 @@ impl FontSystem {
|
|||
&mut self.raw
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`fontdb::Database`].
|
||||
///
|
||||
/// Unlike going through [`raw`](Self::raw) and `db_mut`, this does not
|
||||
/// clear `cosmic-text`'s internal font-match cache.
|
||||
pub fn db(&self) -> &cosmic_text::fontdb::Database {
|
||||
self.raw.db()
|
||||
}
|
||||
|
||||
/// Loads a font from its bytes.
|
||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
if let Cow::Borrowed(bytes) = bytes {
|
||||
|
|
|
|||
|
|
@ -336,6 +336,10 @@ impl editor::Editor for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
Action::ClearSelection => {
|
||||
editor.set_selection(cosmic_text::Selection::None);
|
||||
}
|
||||
|
||||
// Editing events
|
||||
Action::Edit(edit) => {
|
||||
let topmost_line_before_edit = editor
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ workspace = true
|
|||
debug = []
|
||||
selector = ["dep:iced_selector"]
|
||||
a11y = ["iced_accessibility", "iced_core/a11y"]
|
||||
cctk = ["iced_core/cctk", "dep:cctk"]
|
||||
wayland = ["iced_core/wayland", "dep:cctk"]
|
||||
|
||||
[dependencies]
|
||||
bytes.workspace = true
|
||||
|
|
@ -33,9 +33,14 @@ sipper.optional = true
|
|||
iced_selector.workspace = true
|
||||
iced_selector.optional = true
|
||||
iced_futures.features = ["thread-pool"]
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
iced_accessibility.workspace = true
|
||||
iced_accessibility.optional = true
|
||||
window_clipboard.workspace = true
|
||||
dnd.workspace = true
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "../build_helpers" }
|
||||
|
|
|
|||
3
runtime/build.rs
Normal file
3
runtime/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
//! Access the clipboard.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use dnd::{DndDestinationRectangle, DndSurface};
|
||||
use iced_core::clipboard::DndSource;
|
||||
use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
|
||||
use window_clipboard::mime::AllowedMimeTypes;
|
||||
|
||||
use crate::{oneshot, task, Action, Task};
|
||||
use crate::{Action, Task, oneshot, task};
|
||||
|
||||
/// An action to be performed on the system.
|
||||
pub enum DndAction {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ pub use task::Task;
|
|||
pub use user_interface::UserInterface;
|
||||
pub use window::Window;
|
||||
|
||||
use crate::core::Color;
|
||||
use crate::futures::futures::channel::oneshot;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
|
|||
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
/// Platform specific actions defined for wayland
|
||||
pub mod wayland;
|
||||
|
||||
/// Platform specific actions defined for wayland
|
||||
pub enum Action {
|
||||
/// Wayland Specific Actions
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::Action),
|
||||
}
|
||||
|
||||
impl fmt::Debug for Action {
|
||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
Action::Wayland(action) => action.fmt(_f),
|
||||
#[cfg(not(all(feature = "cctk", target_os = "linux")))]
|
||||
#[cfg(not(wayland_platform))]
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl fmt::Debug for Action {
|
|||
f,
|
||||
"Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}",
|
||||
window, token,
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use cctk::sctk::{
|
|||
};
|
||||
use iced_core::layout::Limits;
|
||||
|
||||
use iced_core::window::Id;
|
||||
use iced_core::Rectangle;
|
||||
use iced_core::window::Id;
|
||||
|
||||
/// output for layer surface
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -150,6 +150,18 @@ pub enum Action {
|
|||
/// layer of the layer surface
|
||||
layer: Layer,
|
||||
},
|
||||
/// padding of the surface geometry
|
||||
Padding {
|
||||
/// id of the layer surface
|
||||
id: Id,
|
||||
/// padding of the surface geometry
|
||||
padding: IcedMargin,
|
||||
},
|
||||
ShowOnLock {
|
||||
/// id of the layer surface
|
||||
id: Id,
|
||||
value: bool,
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Action {
|
||||
|
|
@ -162,35 +174,56 @@ impl fmt::Debug for Action {
|
|||
),
|
||||
Action::Size { id, width, height } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height
|
||||
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}",
|
||||
id, width, height
|
||||
),
|
||||
Action::Destroy(id) => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id
|
||||
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}",
|
||||
id
|
||||
),
|
||||
Action::Anchor { id, anchor } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor
|
||||
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}",
|
||||
id, anchor
|
||||
),
|
||||
Action::ExclusiveZone { id, exclusive_zone } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id
|
||||
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}",
|
||||
id
|
||||
),
|
||||
Action::Margin { id, margin } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", id, margin
|
||||
"Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}",
|
||||
id, margin
|
||||
),
|
||||
Action::KeyboardInteractivity { id, keyboard_interactivity } => write!(
|
||||
Action::KeyboardInteractivity {
|
||||
id,
|
||||
keyboard_interactivity,
|
||||
} => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity
|
||||
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}",
|
||||
id, keyboard_interactivity
|
||||
),
|
||||
Action::InputZone { id, zone } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}", id, zone
|
||||
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}",
|
||||
id, zone
|
||||
),
|
||||
Action::Layer { id, layer } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}", id, layer
|
||||
"Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}",
|
||||
id, layer
|
||||
),
|
||||
Action::Padding { id, padding } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::Padding {{ id: {:#?}, padding: {:?} }}",
|
||||
id, padding
|
||||
),
|
||||
Action::ShowOnLock { id, value } => write!(
|
||||
f,
|
||||
"Action::LayerSurfaceAction::ShowOnLock {{ id: {:#?}, value: {:?} }}",
|
||||
id, value
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,26 +124,34 @@ pub enum Action {
|
|||
/// height
|
||||
height: u32,
|
||||
},
|
||||
/// set the size of the popup
|
||||
Reposition {
|
||||
/// id of the popup
|
||||
id: Id,
|
||||
/// positioner of the popup
|
||||
positioner: SctkPositioner,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for Action {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Action::Popup { popup, .. } => write!(
|
||||
f,
|
||||
"Action::PopupAction::Popup {{ popup: {:?} }}",
|
||||
popup
|
||||
),
|
||||
Action::Destroy { id } => write!(
|
||||
f,
|
||||
"Action::PopupAction::Destroy {{ id: {:?} }}",
|
||||
id
|
||||
),
|
||||
Action::Popup { popup, .. } => {
|
||||
write!(f, "Action::PopupAction::Popup {{ popup: {:?} }}", popup)
|
||||
}
|
||||
Action::Destroy { id } => {
|
||||
write!(f, "Action::PopupAction::Destroy {{ id: {:?} }}", id)
|
||||
}
|
||||
Action::Size { id, width, height } => write!(
|
||||
f,
|
||||
"Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}",
|
||||
id, width, height
|
||||
),
|
||||
Action::Reposition { id, positioner } => write!(
|
||||
f,
|
||||
"Action::Reposition {{ id: {:?}, positioner: {:?} }}",
|
||||
id, positioner
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Find and query widgets in your applications.
|
||||
pub use iced_selector::{
|
||||
Bounded, Candidate, Selector, Target, Text, id, is_focused,
|
||||
Bounded, Candidate, Selector, Target, Text, focus, id, is_focused,
|
||||
};
|
||||
|
||||
use crate::Task;
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ pub fn frames() -> Subscription<(Id, Instant)> {
|
|||
_ => None,
|
||||
})
|
||||
}
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
/// Subscribes to the frames of the window of the running application.
|
||||
///
|
||||
/// The resulting [`Subscription`] will produce items at a rate equal to the
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use iced_core::window;
|
||||
|
||||
use crate::Selector;
|
||||
use crate::core::widget::operation::{
|
||||
Focusable, Outcome, Scrollable, TextInput,
|
||||
|
|
@ -100,6 +102,10 @@ where
|
|||
fn finish(&self) -> Self::Output {
|
||||
self.outputs.clone()
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.selector.set_window_id(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Strategy {
|
||||
|
|
@ -110,6 +116,8 @@ pub trait Strategy {
|
|||
fn is_done(&self) -> bool;
|
||||
|
||||
fn finish(&self) -> Self::Output;
|
||||
|
||||
fn set_window_id(&mut self, _id: window::Id) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -273,4 +281,12 @@ where
|
|||
fn finish(&self) -> Outcome<S::Output> {
|
||||
Outcome::Some(self.strategy.finish())
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: window::Id) {
|
||||
self.strategy.set_window_id(id);
|
||||
}
|
||||
|
||||
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||
self.strategy.feed(Candidate::PreOperation { id });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Select data from the widget tree.
|
||||
use iced_core as core;
|
||||
use iced_core::window;
|
||||
|
||||
mod find;
|
||||
mod target;
|
||||
|
|
@ -48,6 +49,8 @@ pub trait Selector {
|
|||
{
|
||||
FindAll::new(find::All::new(self))
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, _id: window::Id) {}
|
||||
}
|
||||
|
||||
impl Selector for &str {
|
||||
|
|
@ -171,3 +174,42 @@ pub fn is_focused() -> impl Selector<Output = Target> {
|
|||
|
||||
IsFocused
|
||||
}
|
||||
|
||||
/// Returns a [`Selector`] that matches widgets that are focusable.
|
||||
pub fn focus() -> impl Selector<Output = (bool, Target, window::Id)> {
|
||||
struct Focus {
|
||||
cur_window_id: window::Id,
|
||||
}
|
||||
|
||||
impl Selector for Focus {
|
||||
type Output = (bool, Target, window::Id);
|
||||
|
||||
fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
|
||||
if let Candidate::Focusable { state, .. } = candidate {
|
||||
Some((
|
||||
state.is_focused(),
|
||||
Target::from(candidate),
|
||||
self.cur_window_id,
|
||||
))
|
||||
} else if matches!(candidate, Candidate::Scrollable { .. }) {
|
||||
Some((false, Target::from(candidate), self.cur_window_id))
|
||||
} else if matches!(candidate, Candidate::PreOperation { .. }) {
|
||||
Some((false, Target::from(candidate), self.cur_window_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"is focusable and focused".to_owned()
|
||||
}
|
||||
|
||||
fn set_window_id(&mut self, id: iced_core::window::Id) {
|
||||
self.cur_window_id = id;
|
||||
}
|
||||
}
|
||||
|
||||
Focus {
|
||||
cur_window_id: window::Id::NONE,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ pub enum Target {
|
|||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
PreOperation {
|
||||
id: Option<Id>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Target {
|
||||
|
|
@ -54,6 +57,7 @@ impl Target {
|
|||
| Target::TextInput { bounds, .. }
|
||||
| Target::Text { bounds, .. }
|
||||
| Target::Custom { bounds, .. } => *bounds,
|
||||
Target::PreOperation { .. } => Rectangle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +70,7 @@ impl Target {
|
|||
| Target::TextInput { visible_bounds, .. }
|
||||
| Target::Text { visible_bounds, .. }
|
||||
| Target::Custom { visible_bounds, .. } => *visible_bounds,
|
||||
Target::PreOperation { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +143,9 @@ impl From<Candidate<'_>> for Target {
|
|||
bounds,
|
||||
visible_bounds,
|
||||
},
|
||||
Candidate::PreOperation { id } => {
|
||||
Self::PreOperation { id: id.cloned() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +203,8 @@ pub enum Candidate<'a> {
|
|||
visible_bounds: Option<Rectangle>,
|
||||
state: &'a dyn Any,
|
||||
},
|
||||
/// Used to mark the beginning of operations for a widget and its children
|
||||
PreOperation { id: Option<&'a Id> },
|
||||
}
|
||||
|
||||
impl<'a> Candidate<'a> {
|
||||
|
|
@ -206,7 +216,8 @@ impl<'a> Candidate<'a> {
|
|||
| Candidate::Scrollable { id, .. }
|
||||
| Candidate::TextInput { id, .. }
|
||||
| Candidate::Text { id, .. }
|
||||
| Candidate::Custom { id, .. } => *id,
|
||||
| Candidate::Custom { id, .. }
|
||||
| Candidate::PreOperation { id } => *id,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +230,7 @@ impl<'a> Candidate<'a> {
|
|||
| Candidate::TextInput { bounds, .. }
|
||||
| Candidate::Text { bounds, .. }
|
||||
| Candidate::Custom { bounds, .. } => *bounds,
|
||||
Candidate::PreOperation { .. } => Rectangle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +243,7 @@ impl<'a> Candidate<'a> {
|
|||
| Candidate::TextInput { visible_bounds, .. }
|
||||
| Candidate::Text { visible_bounds, .. }
|
||||
| Candidate::Custom { visible_bounds, .. } => *visible_bounds,
|
||||
Candidate::PreOperation { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ pub mod font {
|
|||
pub mod event {
|
||||
//! Handle events of a user interface.
|
||||
pub use crate::core::event::PlatformSpecific;
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
pub use crate::core::event::wayland;
|
||||
pub use crate::core::event::{Event, Status};
|
||||
pub use iced_futures::event::{listen, listen_raw, listen_with};
|
||||
|
|
@ -683,6 +683,8 @@ pub use event::Event;
|
|||
pub use executor::Executor;
|
||||
pub use font::Font;
|
||||
pub use renderer::Renderer;
|
||||
#[cfg(feature = "canvas")]
|
||||
pub use renderer::graphics::geometry;
|
||||
pub use task::Task;
|
||||
pub use window::Window;
|
||||
|
||||
|
|
|
|||
|
|
@ -273,6 +273,11 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
runtime::Action::Reload => {
|
||||
// TODO
|
||||
}
|
||||
runtime::Action::Dnd(_)
|
||||
| runtime::Action::PlatformSpecific(_) => {
|
||||
// Drag and drop and platform surfaces need a
|
||||
// windowing system, which an emulator has none of.
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -474,7 +479,9 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
&mut self.renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
icon_color: style.text_color,
|
||||
text_color: style.text_color,
|
||||
scale_factor: f64::from(scale_factor),
|
||||
},
|
||||
mouse::Cursor::Unavailable,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -209,17 +209,19 @@ where
|
|||
&mut self.messages,
|
||||
);
|
||||
|
||||
let scale_factor = 2.0_f32;
|
||||
|
||||
self.raw.draw(
|
||||
&mut self.renderer,
|
||||
theme,
|
||||
&core::renderer::Style {
|
||||
icon_color: base.text_color,
|
||||
text_color: base.text_color,
|
||||
scale_factor: f64::from(scale_factor),
|
||||
},
|
||||
self.cursor,
|
||||
);
|
||||
|
||||
let scale_factor = 2.0;
|
||||
|
||||
let physical_size = Size::new(
|
||||
(self.size.width * scale_factor).round() as u32,
|
||||
(self.size.height * scale_factor).round() as u32,
|
||||
|
|
|
|||
|
|
@ -775,6 +775,7 @@ impl<P: Program + 'static> Tester<P> {
|
|||
}
|
||||
_ => None,
|
||||
},
|
||||
..text::Style::default()
|
||||
})
|
||||
.into()
|
||||
},
|
||||
|
|
@ -945,6 +946,7 @@ where
|
|||
text(label).size(14).style(|theme: &core::Theme| {
|
||||
text::Style {
|
||||
color: Some(theme.extended_palette().background.weak.text),
|
||||
..text::Style::default()
|
||||
}
|
||||
}),
|
||||
space::horizontal(),
|
||||
|
|
|
|||
|
|
@ -263,6 +263,10 @@ impl Engine {
|
|||
} else {
|
||||
// Draw corners that have too small border radii as having no border radius,
|
||||
// but mask them with the rounded rectangle with the correct border radius.
|
||||
if path_bounds.width < 1.0 || path_bounds.height < 1.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut temp_pixmap = tiny_skia::Pixmap::new(
|
||||
path_bounds.width as u32,
|
||||
path_bounds.height as u32,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,21 @@ impl Pipeline {
|
|||
) {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
|
||||
let Ok(mut image) = cache.allocate(handle) else {
|
||||
let target = {
|
||||
let Ok(image) = cache.allocate(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
graphics::image::downsample_target(
|
||||
Size::new(image.width(), image.height()),
|
||||
bounds.size(),
|
||||
)
|
||||
};
|
||||
|
||||
let Ok(mut image) = (match target {
|
||||
Some(target) => cache.allocate_resampled(handle, target),
|
||||
None => cache.allocate(handle),
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -110,6 +124,8 @@ impl Pipeline {
|
|||
struct Cache {
|
||||
entries: FxHashMap<raster::Id, Option<Entry>>,
|
||||
hits: FxHashSet<raster::Id>,
|
||||
resampled: FxHashMap<(raster::Id, u32, u32), Entry>,
|
||||
resampled_hits: FxHashSet<(raster::Id, u32, u32)>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
|
|
@ -166,9 +182,59 @@ impl Cache {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Like [`Self::allocate`], resampled to `target`. Call after
|
||||
/// [`Self::allocate`] has decoded the image.
|
||||
pub fn allocate_resampled(
|
||||
&mut self,
|
||||
handle: &raster::Handle,
|
||||
target: Size<u32>,
|
||||
) -> Result<tiny_skia::PixmapRef<'_>, raster::Error> {
|
||||
let key = (handle.id(), target.width, target.height);
|
||||
|
||||
if !self.resampled.contains_key(&key) {
|
||||
let native = self
|
||||
.entries
|
||||
.get(&handle.id())
|
||||
.and_then(Option::as_ref)
|
||||
.ok_or(raster::Error::Empty)?;
|
||||
|
||||
// Stored pixels are already premultiplied, so resample them as is.
|
||||
let pixels = graphics::image::downsample_premultiplied(
|
||||
bytemuck::cast_slice(&native.pixels),
|
||||
Size::new(native.width, native.height),
|
||||
target,
|
||||
);
|
||||
|
||||
let _ = self.resampled.insert(
|
||||
key,
|
||||
Entry {
|
||||
width: target.width,
|
||||
height: target.height,
|
||||
pixels: pixels
|
||||
.chunks_exact(4)
|
||||
.map(|p| u32::from_ne_bytes([p[0], p[1], p[2], p[3]]))
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let _ = self.resampled_hits.insert(key);
|
||||
let entry = &self.resampled[&key];
|
||||
|
||||
Ok(tiny_skia::PixmapRef::from_bytes(
|
||||
bytemuck::cast_slice(&entry.pixels),
|
||||
entry.width,
|
||||
entry.height,
|
||||
)
|
||||
.expect("Build pixmap from image bytes"))
|
||||
}
|
||||
|
||||
fn trim(&mut self) {
|
||||
self.entries.retain(|key, _| self.hits.contains(key));
|
||||
self.resampled
|
||||
.retain(|key, _| self.resampled_hits.contains(key));
|
||||
self.hits.clear();
|
||||
self.resampled_hits.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::core::svg::{Data, Handle};
|
||||
use crate::core::{Color, Rectangle, Size};
|
||||
use crate::graphics::text;
|
||||
|
||||
use resvg::usvg;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
|
@ -74,6 +75,7 @@ struct Cache {
|
|||
rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
|
||||
raster_hits: FxHashSet<RasterKey>,
|
||||
fontdb: Option<Arc<usvg::fontdb::Database>>,
|
||||
fontdb_version: Option<text::Version>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -87,12 +89,15 @@ impl Cache {
|
|||
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
||||
let id = handle.id();
|
||||
|
||||
// TODO: Reuse `cosmic-text` font database
|
||||
if self.fontdb.is_none() {
|
||||
let mut fontdb = usvg::fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
// Reuse the font database already loaded by the `cosmic-text` font
|
||||
// system. We clone it once and refresh only when a font is
|
||||
// loaded (i.e. when the font system's version changes).
|
||||
let font_system = text::font_system().read().expect("Read font system");
|
||||
let version = font_system.version();
|
||||
|
||||
self.fontdb = Some(Arc::new(fontdb));
|
||||
if self.fontdb_version != Some(version) {
|
||||
self.fontdb = Some(Arc::new(font_system.db().clone()));
|
||||
self.fontdb_version = Some(version);
|
||||
}
|
||||
|
||||
let options = usvg::Options {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ svg = ["iced_graphics/svg", "resvg/text"]
|
|||
web-colors = ["iced_graphics/web-colors"]
|
||||
webgl = ["wgpu/webgl"]
|
||||
strict-assertions = []
|
||||
# Yoda: opt-in X11 backend. Pulls tiny-xlib/as-raw-xcb-connection/x11rb
|
||||
# (which otherwise were unconditional direct deps on unix). Wayland-only
|
||||
# builds just leave this off — window/x11.rs and the DRI3 device-id
|
||||
# fallback in window/compositor.rs are cfg-gated on it.
|
||||
x11 = ["dep:as-raw-xcb-connection", "dep:tiny-xlib", "dep:x11rb"]
|
||||
wayland = ["cctk"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
|
|
@ -57,11 +63,14 @@ wayland-protocols.workspace = true
|
|||
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||
wayland-client = { version = "0.31.2" }
|
||||
wayland-sys = { version = "0.31.1", features = ["dlopen"] }
|
||||
as-raw-xcb-connection = "1.0.1"
|
||||
tiny-xlib = "0.2.3"
|
||||
x11rb = { version = "0.13.1", features = [
|
||||
as-raw-xcb-connection = { version = "1.0.1", optional = true }
|
||||
tiny-xlib = { version = "0.2.3", optional = true }
|
||||
x11rb = { version = "0.13.1", optional = true, features = [
|
||||
"allow-unsafe-code",
|
||||
"dl-libxcb",
|
||||
"dri3",
|
||||
"randr",
|
||||
] }
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "../build_helpers" }
|
||||
|
|
|
|||
3
wgpu/build.rs
Normal file
3
wgpu/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
|
|
@ -63,7 +63,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
/// Returns the size of the written bytes.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
offset: usize,
|
||||
|
|
@ -79,7 +78,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
MAX_WRITE_SIZE_U64,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(
|
||||
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
|
||||
|
|
@ -100,7 +98,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
bytes_left,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(&bytes[bytes_written..]);
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ pub fn convert(
|
|||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
|
|
@ -149,7 +149,7 @@ pub fn convert(
|
|||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
@ -194,6 +194,7 @@ pub fn convert(
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&pipeline);
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ impl Atlas {
|
|||
match &entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
self.upload_allocation(
|
||||
pixels, width, 0, allocation, device, encoder, belt,
|
||||
pixels, width, 0, allocation, encoder, belt,
|
||||
);
|
||||
}
|
||||
Entry::Fragmented { fragments, .. } => {
|
||||
|
|
@ -145,7 +145,6 @@ impl Atlas {
|
|||
width,
|
||||
offset,
|
||||
&fragment.allocation,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
);
|
||||
|
|
@ -312,7 +311,6 @@ impl Atlas {
|
|||
image_width: u32,
|
||||
offset: usize,
|
||||
allocation: &Allocation,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
) {
|
||||
|
|
@ -334,7 +332,6 @@ impl Atlas {
|
|||
let buffer_slice = belt.allocate(
|
||||
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
|
||||
wgpu::BufferSize::new(8 * 4).unwrap(),
|
||||
device,
|
||||
);
|
||||
|
||||
const PIXEL: usize = 4;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use crate::image::atlas::{self, Atlas};
|
|||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
use worker::Worker;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
#[cfg(feature = "image")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
@ -38,7 +40,13 @@ impl Cache {
|
|||
raster: Raster {
|
||||
cache: crate::image::raster::Cache::default(),
|
||||
pending: HashMap::new(),
|
||||
belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024),
|
||||
resampled: FxHashMap::default(),
|
||||
resampled_hits: FxHashSet::default(),
|
||||
should_trim: false,
|
||||
belt: wgpu::util::StagingBelt::new(
|
||||
device.clone(),
|
||||
2 * 1024 * 1024,
|
||||
),
|
||||
},
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache::default(),
|
||||
|
|
@ -206,7 +214,9 @@ impl Cache {
|
|||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::image::Handle,
|
||||
bounds: Size<f32>,
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
use crate::graphics::image::{downsample, downsample_target, load};
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
self.receive();
|
||||
|
|
@ -220,6 +230,34 @@ impl Cache {
|
|||
None,
|
||||
)?;
|
||||
|
||||
if let Some(target) = downsample_target(memory.dimensions(), bounds) {
|
||||
let key = (handle.id(), target.width, target.height);
|
||||
|
||||
if !self.raster.resampled.contains_key(&key) {
|
||||
let image = memory.host().or_else(|| load(handle).ok())?;
|
||||
let image = downsample(&image, target);
|
||||
|
||||
let entry = self.atlas.upload(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
target.width,
|
||||
target.height,
|
||||
&image,
|
||||
)?;
|
||||
|
||||
let _ = self.raster.resampled.insert(key, entry);
|
||||
self.raster.should_trim = true;
|
||||
}
|
||||
|
||||
let _ = self.raster.resampled_hits.insert(key);
|
||||
|
||||
return Some((
|
||||
self.raster.resampled.get(&key)?,
|
||||
self.atlas.bind_group(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
|
|
@ -300,6 +338,23 @@ impl Cache {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.drop(_bind_group);
|
||||
});
|
||||
|
||||
if self.raster.should_trim {
|
||||
let hits = &self.raster.resampled_hits;
|
||||
let atlas = &mut self.atlas;
|
||||
|
||||
self.raster.resampled.retain(|key, entry| {
|
||||
let retain = hits.contains(key);
|
||||
|
||||
if !retain {
|
||||
atlas.remove(entry);
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
self.raster.resampled_hits.clear();
|
||||
self.raster.should_trim = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
|
|
@ -373,6 +428,9 @@ impl Drop for Cache {
|
|||
struct Raster {
|
||||
cache: crate::image::raster::Cache,
|
||||
pending: HashMap<core::image::Id, Vec<Callback>>,
|
||||
resampled: FxHashMap<(core::image::Id, u32, u32), atlas::Entry>,
|
||||
resampled_hits: FxHashSet<(core::image::Id, u32, u32)>,
|
||||
should_trim: bool,
|
||||
belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +509,10 @@ mod worker {
|
|||
backend,
|
||||
texture_layout,
|
||||
shell: shell.clone(),
|
||||
belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
|
||||
belt: wgpu::util::StagingBelt::new(
|
||||
device.clone(),
|
||||
4 * 1024 * 1024,
|
||||
),
|
||||
jobs: jobs_receiver,
|
||||
output: work_sender,
|
||||
quit: quit_receiver,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl Pipeline {
|
|||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ impl Pipeline {
|
|||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
|
@ -106,8 +106,8 @@ impl Pipeline {
|
|||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader =
|
||||
|
|
@ -191,7 +191,7 @@ impl Pipeline {
|
|||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
@ -267,7 +267,13 @@ impl State {
|
|||
clip_bounds,
|
||||
} => {
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_raster(device, encoder, belt, &image.handle)
|
||||
.upload_raster(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&image.handle,
|
||||
bounds.size() * scale,
|
||||
)
|
||||
{
|
||||
match atlas.as_mut() {
|
||||
None => {
|
||||
|
|
@ -528,7 +534,6 @@ impl Layer {
|
|||
&self.uniforms,
|
||||
0,
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
|
|
@ -539,11 +544,11 @@ impl Layer {
|
|||
let mut offset = 0;
|
||||
|
||||
if !nearest.is_empty() {
|
||||
offset += self.instances.write(device, encoder, belt, 0, nearest);
|
||||
offset += self.instances.write(encoder, belt, 0, nearest);
|
||||
}
|
||||
|
||||
if !linear.is_empty() {
|
||||
let _ = self.instances.write(device, encoder, belt, offset, linear);
|
||||
let _ = self.instances.write(encoder, belt, offset, linear);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ impl Renderer {
|
|||
// It would be great if the `StagingBelt` API exposed methods
|
||||
// for introspection to detect when a resize may be worth it.
|
||||
staging_belt: wgpu::util::StagingBelt::new(
|
||||
engine.device.clone(),
|
||||
buffer::MAX_WRITE_SIZE as u64,
|
||||
),
|
||||
|
||||
|
|
@ -454,6 +455,7 @@ impl Renderer {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
},
|
||||
));
|
||||
|
||||
|
|
@ -528,6 +530,7 @@ impl Renderer {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
|
@ -621,6 +624,7 @@ impl Renderer {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ impl Layer {
|
|||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
self.update(device, encoder, belt, transformation, scale);
|
||||
self.update(encoder, belt, transformation, scale);
|
||||
|
||||
if !quads.solids.is_empty() {
|
||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||
|
|
@ -223,7 +223,6 @@ impl Layer {
|
|||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
transformation: Transformation,
|
||||
|
|
@ -237,7 +236,6 @@ impl Layer {
|
|||
&self.constants_buffer,
|
||||
0,
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ impl Layer {
|
|||
instances: &[Gradient],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
let _ = self.instances.write(encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
|
@ -75,8 +75,8 @@ impl Pipeline {
|
|||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.quad.gradient.pipeline"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[constants_layout],
|
||||
immediate_size: 0,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ impl Pipeline {
|
|||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl Layer {
|
|||
instances: &[Solid],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
let _ = self.instances.write(encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
|
@ -65,8 +65,8 @@ impl Pipeline {
|
|||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.quad.solid.pipeline"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[constants_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader =
|
||||
|
|
@ -139,7 +139,7 @@ impl Pipeline {
|
|||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ fn prepare(
|
|||
)?;
|
||||
|
||||
Some(cryoglyph::TextArea {
|
||||
buffer,
|
||||
text: buffer.layout_runs(),
|
||||
left: position.x,
|
||||
top: position.y,
|
||||
scale: transformation.scale_factor()
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ fn render<'a>(
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
})
|
||||
};
|
||||
|
||||
|
|
@ -425,18 +426,13 @@ impl Layer {
|
|||
|
||||
let indices = mesh.indices();
|
||||
|
||||
index_offset += self.index_buffer.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
index_offset,
|
||||
indices,
|
||||
);
|
||||
index_offset +=
|
||||
self.index_buffer
|
||||
.write(encoder, belt, index_offset, indices);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
solid_vertex_offset += self.solid.vertices.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_vertex_offset,
|
||||
|
|
@ -444,16 +440,14 @@ impl Layer {
|
|||
);
|
||||
|
||||
solid_uniform_offset += self.solid.uniforms.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_uniform_offset,
|
||||
&[uniforms],
|
||||
);
|
||||
)
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
gradient_vertex_offset += self.gradient.vertices.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_vertex_offset,
|
||||
|
|
@ -461,7 +455,6 @@ impl Layer {
|
|||
);
|
||||
|
||||
gradient_uniform_offset += self.gradient.uniforms.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_uniform_offset,
|
||||
|
|
@ -731,7 +724,7 @@ mod solid {
|
|||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -784,7 +777,7 @@ mod solid {
|
|||
primitive: triangle::primitive_state(),
|
||||
depth_stencil: None,
|
||||
multisample: triangle::multisample_state(antialiasing),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
|
@ -886,7 +879,7 @@ mod gradient {
|
|||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -949,7 +942,7 @@ mod gradient {
|
|||
primitive: triangle::primitive_state(),
|
||||
depth_stencil: None,
|
||||
multisample: triangle::multisample_state(antialiasing),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ impl Pipeline {
|
|||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader =
|
||||
|
|
@ -116,7 +116,7 @@ impl Pipeline {
|
|||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
|
|
@ -177,6 +177,7 @@ impl Pipeline {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +321,6 @@ impl State {
|
|||
0,
|
||||
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
||||
.expect("non-empty ratio"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
||||
|
||||
|
|
@ -351,6 +351,7 @@ impl State {
|
|||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&pipeline.raw);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
//! Display rendering results on windows.
|
||||
pub mod compositor;
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "cctk",
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "redox")
|
||||
))]
|
||||
#[cfg(wayland_platform)]
|
||||
mod wayland;
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))]
|
||||
mod x11;
|
||||
|
||||
pub use compositor::Compositor;
|
||||
|
|
|
|||
|
|
@ -9,16 +9,10 @@ use crate::graphics::{self, Shell, Viewport};
|
|||
use crate::settings::{self, Settings};
|
||||
use crate::{Engine, Renderer};
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "cctk",
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "redox")
|
||||
))]
|
||||
#[cfg(wayland_platform)]
|
||||
use super::wayland::get_wayland_device_ids;
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))]
|
||||
use super::x11::get_x11_device_ids;
|
||||
use std::future::Future;
|
||||
|
||||
/// A window graphics backend for iced powered by `wgpu`.
|
||||
pub struct Compositor {
|
||||
|
|
@ -65,15 +59,12 @@ impl Compositor {
|
|||
compatible_window: Option<W>,
|
||||
shell: Shell,
|
||||
) -> Result<Self, Error> {
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "cctk",
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "redox")
|
||||
))]
|
||||
#[cfg(wayland_platform)]
|
||||
let ids = compatible_window.as_ref().and_then(|window| {
|
||||
get_wayland_device_ids(window)
|
||||
.or_else(|| get_x11_device_ids(window))
|
||||
let w = get_wayland_device_ids(window);
|
||||
#[cfg(feature = "x11")]
|
||||
let w = w.or_else(|| get_x11_device_ids(window));
|
||||
w
|
||||
});
|
||||
|
||||
// HACK:
|
||||
|
|
@ -82,12 +73,7 @@ impl Compositor {
|
|||
// 3. and the user didn't set an adapter name,
|
||||
// 4. and the user didn't request the high power pref
|
||||
// => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "cctk",
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "redox")
|
||||
))]
|
||||
#[cfg(wayland_platform)]
|
||||
if !matches!(ids, Some((0x10de, _)))
|
||||
&& std::env::var_os("__NV_PRIME_RENDER_OFFLOAD")
|
||||
.is_none_or(|var| var == "0")
|
||||
|
|
@ -126,6 +112,7 @@ impl Compositor {
|
|||
// if log::max_level() >= log::LevelFilter::Info {
|
||||
// let available_adapters: Vec<_> = instance
|
||||
// .enumerate_adapters(settings.backends)
|
||||
// .await
|
||||
// .iter()
|
||||
// .map(wgpu::Adapter::get_info)
|
||||
// .collect();
|
||||
|
|
@ -146,15 +133,11 @@ impl Compositor {
|
|||
let mut adapter = None;
|
||||
#[cfg_attr(not(unix), allow(dead_code))]
|
||||
if std::env::var_os("WGPU_ADAPTER_NAME").is_none() {
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "cctk",
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "redox")
|
||||
))]
|
||||
#[cfg(wayland_platform)]
|
||||
if let Some((vendor_id, device_id)) = ids {
|
||||
adapter = instance
|
||||
.enumerate_adapters(settings.backends)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|adapter| {
|
||||
let info = adapter.get_info();
|
||||
|
|
@ -172,6 +155,7 @@ impl Compositor {
|
|||
} else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") {
|
||||
adapter = instance
|
||||
.enumerate_adapters(settings.backends)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|adapter| {
|
||||
let info = adapter.get_info();
|
||||
|
|
@ -184,6 +168,8 @@ impl Compositor {
|
|||
true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
adapter = instance.request_adapter(&adapter_options).await.ok();
|
||||
}
|
||||
|
||||
let adapter = match adapter {
|
||||
|
|
@ -286,6 +272,14 @@ impl Compositor {
|
|||
..limits
|
||||
});
|
||||
|
||||
// Request SHADER_F16 only if the adapter supports it (e.g., not available in WebGL2)
|
||||
let required_features =
|
||||
if adapter.features().contains(wgpu::Features::SHADER_F16) {
|
||||
wgpu::Features::SHADER_F16
|
||||
} else {
|
||||
wgpu::Features::empty()
|
||||
};
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for required_limits in limits {
|
||||
|
|
@ -294,7 +288,7 @@ impl Compositor {
|
|||
label: Some(
|
||||
"iced_wgpu::window::compositor device descriptor",
|
||||
),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_features,
|
||||
required_limits: required_limits.clone(),
|
||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||
trace: wgpu::Trace::Off,
|
||||
|
|
@ -352,6 +346,14 @@ pub fn present(
|
|||
) -> Result<(), compositor::SurfaceError> {
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
if frame.texture.width() != viewport.physical_width()
|
||||
|| frame.texture.height() != viewport.physical_height()
|
||||
{
|
||||
return Err(compositor::SurfaceError::Outdated);
|
||||
}
|
||||
|
||||
let suboptimal = frame.suboptimal;
|
||||
|
||||
let view = &frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
|
@ -367,6 +369,10 @@ pub fn present(
|
|||
on_pre_present();
|
||||
frame.present();
|
||||
|
||||
if suboptimal {
|
||||
return Err(compositor::SurfaceError::Outdated);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => match error {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
io::{BufRead, BufReader},
|
||||
|
|
@ -144,7 +146,9 @@ pub fn get_x11_device_ids<W: Window>(window: &W) -> Option<(u16, u16)> {
|
|||
{
|
||||
let stat =
|
||||
stat(Path::new("/dev/dri").join(device)).ok()?;
|
||||
let dev = stat.st_rdev;
|
||||
// Hack (u64 as u64) for E2K CPU architecture
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let dev = stat.st_rdev as u64;
|
||||
return super::ids_from_dev(dev);
|
||||
}
|
||||
}
|
||||
|
|
@ -165,7 +169,9 @@ pub fn get_x11_device_ids<W: Window>(window: &W) -> Option<(u16, u16)> {
|
|||
let dri3 = conn.dri3_open(root, x11rb::NONE).ok()?.reply().ok()?;
|
||||
let device_fd = dri3.device_fd;
|
||||
let stat = fstat(device_fd).ok()?;
|
||||
let dev = stat.st_rdev;
|
||||
// Hack (u64 as u64) for E2K CPU architecture
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let dev = stat.st_rdev as u64;
|
||||
super::ids_from_dev(dev)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,15 +29,13 @@ highlighter = ["dep:iced_highlighter"]
|
|||
advanced = []
|
||||
crisp = []
|
||||
a11y = ["iced_accessibility"]
|
||||
cctk = ["iced_runtime/cctk", "dep:cctk"]
|
||||
wayland = ["iced_runtime/wayland", "dep:cctk"]
|
||||
|
||||
[dependencies]
|
||||
iced_renderer.workspace = true
|
||||
iced_runtime.workspace = true
|
||||
iced_accessibility.workspace = true
|
||||
iced_accessibility.optional = true
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
num-traits.workspace = true
|
||||
log.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
|
@ -57,3 +55,10 @@ pulldown-cmark.optional = true
|
|||
|
||||
iced_highlighter.workspace = true
|
||||
iced_highlighter.optional = true
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
|
||||
cctk.workspace = true
|
||||
cctk.optional = true
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "../build_helpers" }
|
||||
|
|
|
|||
3
widget/build.rs
Normal file
3
widget/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
|
|
@ -267,6 +267,7 @@ struct State {
|
|||
is_hovered: bool,
|
||||
is_pressed: bool,
|
||||
is_focused: bool,
|
||||
needs_redraw: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
|
|
@ -368,6 +369,12 @@ where
|
|||
viewport,
|
||||
);
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
if state.needs_redraw {
|
||||
state.needs_redraw = false;
|
||||
shell.request_redraw();
|
||||
}
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1000,10 +1007,12 @@ impl operation::Focusable for State {
|
|||
}
|
||||
|
||||
fn focus(&mut self) {
|
||||
self.needs_redraw |= !self.is_focused;
|
||||
self.is_focused = true;
|
||||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
self.needs_redraw |= self.is_focused;
|
||||
self.is_focused = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -505,6 +505,7 @@ where
|
|||
state.raw(),
|
||||
crate::text::Style {
|
||||
color: style.text_color,
|
||||
..crate::text::Style::default()
|
||||
},
|
||||
viewport,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ use crate::core::{
|
|||
color,
|
||||
};
|
||||
|
||||
use iced_runtime::{Action, Task, task};
|
||||
use iced_runtime::{Task, task};
|
||||
|
||||
/// A widget that aligns its contents inside of its boundaries.
|
||||
///
|
||||
|
|
@ -601,7 +601,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
|
|||
operate: &mut dyn FnMut(&mut dyn Operation<Option<Rectangle>>),
|
||||
) {
|
||||
self.depth += 1;
|
||||
self.traverse(operate);
|
||||
operate(self);
|
||||
self.depth -= 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
//! Keyed columns distribute content vertically while keeping continuity.
|
||||
//! Distribute content vertically.
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
|
|
@ -293,9 +292,12 @@ where
|
|||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), c_layout)| {
|
||||
child
|
||||
.as_widget_mut()
|
||||
.operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation);
|
||||
child.as_widget_mut().operate(
|
||||
state,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -318,7 +320,13 @@ where
|
|||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget_mut().update(
|
||||
tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell,
|
||||
tree,
|
||||
event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
|
@ -337,9 +345,13 @@ where
|
|||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((child, tree), c_layout)| {
|
||||
child
|
||||
.as_widget()
|
||||
.mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer)
|
||||
child.as_widget().mouse_interaction(
|
||||
tree,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ pub use iced_renderer::graphics;
|
|||
|
||||
pub use core::widget::Id;
|
||||
|
||||
pub(crate) const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
|
||||
|
||||
mod action;
|
||||
mod mouse_area;
|
||||
mod pin;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(missing_docs)]
|
||||
use crate::core::event::{self, Event};
|
||||
use crate::core::event::Event;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
|
|
@ -363,24 +363,25 @@ where
|
|||
let state = tree.state.downcast_mut::<State>();
|
||||
let offset = layout.position() - Point::ORIGIN;
|
||||
|
||||
self.visible_elements
|
||||
for (element, (index, layout, tree)) in self
|
||||
.visible_elements
|
||||
.iter_mut()
|
||||
.zip(&mut state.visible_layouts)
|
||||
.map(|(element, (index, layout, tree))| {
|
||||
element.as_widget_mut().update(
|
||||
tree,
|
||||
event,
|
||||
Layout::with_offset(
|
||||
offset + Vector::new(0.0, self.spacing * *index as f32),
|
||||
layout,
|
||||
),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
});
|
||||
{
|
||||
element.as_widget_mut().update(
|
||||
tree,
|
||||
event,
|
||||
Layout::with_offset(
|
||||
offset + Vector::new(0.0, self.spacing * *index as f32),
|
||||
layout,
|
||||
),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
if let Event::Window(window::Event::RedrawRequested(_)) = event {
|
||||
match &mut state.task {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use iced_renderer::core::mouse::Click;
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ use crate::core::{
|
|||
};
|
||||
use log::trace;
|
||||
|
||||
const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
|
||||
use crate::DRAG_DEADBAND_DISTANCE;
|
||||
|
||||
const THICKNESS_RATIO: f32 = 25.0;
|
||||
|
||||
/// A collection of panes distributed using either vertical or horizontal splits
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::core::renderer;
|
|||
use crate::core::widget::{self, Tree};
|
||||
use crate::core::{
|
||||
self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size,
|
||||
Vector, event,
|
||||
Vector,
|
||||
};
|
||||
use crate::pane_grid::{Draggable, TitleBar};
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ where
|
|||
width: Length,
|
||||
height: Length,
|
||||
position: Point,
|
||||
overflow: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
|
||||
|
|
@ -75,6 +76,7 @@ where
|
|||
width: Length::Fill,
|
||||
height: Length::Fill,
|
||||
position: Point::ORIGIN,
|
||||
overflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +109,12 @@ where
|
|||
self.position.y = y.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the [`Pin`] can overflow its boundaries.
|
||||
pub fn overflow(mut self, overflow: bool) -> Self {
|
||||
self.overflow = overflow;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
|
|
@ -146,7 +154,11 @@ where
|
|||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
let available =
|
||||
limits.max() - Size::new(self.position.x, self.position.y);
|
||||
if self.overflow {
|
||||
limits.max()
|
||||
} else {
|
||||
limits.max() - Size::new(self.position.x, self.position.y)
|
||||
};
|
||||
|
||||
let node = self
|
||||
.content
|
||||
|
|
|
|||
|
|
@ -459,6 +459,7 @@ where
|
|||
state.raw(),
|
||||
crate::text::Style {
|
||||
color: style.text_color,
|
||||
..crate::text::Style::default()
|
||||
},
|
||||
viewport,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ use iced_runtime::core::widget::Id;
|
|||
#[cfg(feature = "a11y")]
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::keyboard;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
|
|
@ -248,64 +247,19 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets whether the user should be allowed to auto-scroll the [`Scrollable`]
|
||||
/// with the middle mouse button.
|
||||
/// Adds padding at the ends of the [`Scrollbar`]s of the [`Scrollable`].
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
pub fn auto_scroll(mut self, auto_scroll: bool) -> Self {
|
||||
self.auto_scroll = auto_scroll;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the scrollbar width of the [`Scrollbar`].
|
||||
pub fn scrollbar_width(mut self, width: impl Into<Pixels>) -> Self {
|
||||
let width = width.into().0.max(0.0);
|
||||
/// The `padding` provided will be used as space at the top and bottom of a
|
||||
/// vertical [`Scrollbar`], and at the left and right ends of a horizontal
|
||||
/// [`Scrollbar`], when they are visible.
|
||||
///
|
||||
/// Unlike [`Self::spacing`], the padding does not affect the layout of the
|
||||
/// [`Scrollable`].
|
||||
pub fn padding(mut self, new_padding: impl Into<Pixels>) -> Self {
|
||||
let padding = new_padding.into().0;
|
||||
|
||||
match &mut self.direction {
|
||||
Direction::Horizontal(scrollbar)
|
||||
| Direction::Vertical(scrollbar) => {
|
||||
scrollbar.width = width;
|
||||
}
|
||||
Direction::Both {
|
||||
horizontal,
|
||||
vertical,
|
||||
} => {
|
||||
horizontal.width = width;
|
||||
vertical.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the scroller width of the [`Scrollbar`].
|
||||
pub fn scroller_width(mut self, width: impl Into<Pixels>) -> Self {
|
||||
let width = width.into().0.max(0.0);
|
||||
|
||||
match &mut self.direction {
|
||||
Direction::Horizontal(scrollbar)
|
||||
| Direction::Vertical(scrollbar) => {
|
||||
scrollbar.scroller_width = width;
|
||||
}
|
||||
Direction::Both {
|
||||
horizontal,
|
||||
vertical,
|
||||
} => {
|
||||
horizontal.scroller_width = width;
|
||||
vertical.scroller_width = width;
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding at the start and end of the [`Scrollbar`].
|
||||
pub fn scrollbar_padding(mut self, padding: impl Into<Pixels>) -> Self {
|
||||
let padding = padding.into().0.max(0.0);
|
||||
|
||||
match &mut self.direction {
|
||||
Direction::Horizontal(scrollbar)
|
||||
| Direction::Vertical(scrollbar) => {
|
||||
Direction::Horizontal(scrollbar) | Direction::Vertical(scrollbar) => {
|
||||
scrollbar.padding = padding;
|
||||
}
|
||||
Direction::Both {
|
||||
|
|
@ -320,6 +274,15 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets whether the user should be allowed to auto-scroll the [`Scrollable`]
|
||||
/// with the middle mouse button.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
pub fn auto_scroll(mut self, auto_scroll: bool) -> Self {
|
||||
self.auto_scroll = auto_scroll;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of this [`Scrollable`].
|
||||
#[must_use]
|
||||
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
||||
|
|
@ -504,9 +467,15 @@ impl Scrollbar {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the padding at the start and end of the [`Scrollbar`].
|
||||
/// Sets the padding of the [`Scrollbar`].
|
||||
///
|
||||
/// The padding is added at the top and bottom of the scrollbar (or at the
|
||||
/// left and right ends for a horizontal [`Scrollbar`]) when it is visible.
|
||||
///
|
||||
/// Unlike [`Self::margin`] and [`Self::spacing`], the padding does not
|
||||
/// affect the layout of the [`Scrollable`].
|
||||
pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
|
||||
self.padding = padding.into().0.max(0.0);
|
||||
self.padding = padding.into().0;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -676,13 +645,7 @@ where
|
|||
let translation =
|
||||
state.translation(self.direction, bounds, content_bounds);
|
||||
|
||||
operation.scrollable(
|
||||
Some(&self.id),
|
||||
bounds,
|
||||
content_bounds,
|
||||
translation,
|
||||
state,
|
||||
);
|
||||
operation.pre_operation(Some(&self.id));
|
||||
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
|
|
@ -696,6 +659,14 @@ where
|
|||
operation,
|
||||
);
|
||||
});
|
||||
// XXX must be done after traversing to perform DFS
|
||||
operation.scrollable(
|
||||
Some(&self.id),
|
||||
bounds,
|
||||
content_bounds,
|
||||
translation,
|
||||
state,
|
||||
);
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
@ -933,7 +904,7 @@ where
|
|||
|
||||
let had_input_method = shell.input_method().is_enabled();
|
||||
|
||||
let mut c_event = match event.clone() {
|
||||
let c_event = match event.clone() {
|
||||
Event::Dnd(dnd::DndEvent::Offer(
|
||||
id,
|
||||
dnd::OfferEvent::Enter {
|
||||
|
|
@ -961,6 +932,16 @@ where
|
|||
y: y + translation.y as f64,
|
||||
},
|
||||
)),
|
||||
Event::Touch(touch::Event::FingerLifted {
|
||||
id,
|
||||
position,
|
||||
}) if matches!(
|
||||
state.interaction,
|
||||
Interaction::TouchScrolling(_)
|
||||
) =>
|
||||
{
|
||||
Event::Touch(touch::Event::FingerLost { id, position })
|
||||
}
|
||||
e => e,
|
||||
};
|
||||
self.content.as_widget_mut().update(
|
||||
|
|
@ -988,6 +969,14 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||
| Event::Touch(touch::Event::FingerPressed { .. })
|
||||
) {
|
||||
state.suppress_touch_hover = false;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
|
|
@ -996,10 +985,18 @@ where
|
|||
| touch::Event::FingerLost { .. }
|
||||
)
|
||||
) {
|
||||
if matches!(state.interaction, Interaction::TouchScrolling(_)) {
|
||||
state.suppress_touch_hover = true;
|
||||
}
|
||||
state.interaction = Interaction::None;
|
||||
state.touch_press_start = None;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Event::Touch(touch::Event::FingerPressed { .. }) = event {
|
||||
state.touch_press_start = cursor_over_scrollable;
|
||||
}
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1093,14 +1090,31 @@ where
|
|||
Interaction::TouchScrolling(position);
|
||||
}
|
||||
touch::Event::FingerMoved { .. } => {
|
||||
let Interaction::TouchScrolling(
|
||||
scroll_box_touched_at,
|
||||
) = state.interaction
|
||||
let Some(cursor_position) = cursor.position()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(cursor_position) = cursor.position()
|
||||
if !matches!(
|
||||
state.interaction,
|
||||
Interaction::TouchScrolling(_)
|
||||
) {
|
||||
let Some(start) = state.touch_press_start
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if start.distance(cursor_position)
|
||||
< crate::DRAG_DEADBAND_DISTANCE
|
||||
{
|
||||
return;
|
||||
}
|
||||
state.interaction =
|
||||
Interaction::TouchScrolling(start);
|
||||
}
|
||||
|
||||
let Interaction::TouchScrolling(
|
||||
scroll_box_touched_at,
|
||||
) = state.interaction
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1322,6 +1336,14 @@ where
|
|||
state.translation(self.direction, bounds, content_bounds);
|
||||
|
||||
let cursor = match cursor_over_scrollable {
|
||||
_ if state.suppress_touch_hover
|
||||
|| matches!(
|
||||
state.interaction,
|
||||
Interaction::TouchScrolling(_)
|
||||
) =>
|
||||
{
|
||||
mouse::Cursor::Unavailable
|
||||
}
|
||||
Some(cursor_position)
|
||||
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
||||
{
|
||||
|
|
@ -1497,6 +1519,14 @@ where
|
|||
state.translation(self.direction, bounds, content_bounds);
|
||||
|
||||
let cursor = match cursor_over_scrollable {
|
||||
_ if state.suppress_touch_hover
|
||||
|| matches!(
|
||||
state.interaction,
|
||||
Interaction::TouchScrolling(_)
|
||||
) =>
|
||||
{
|
||||
cursor.levitate() + translation
|
||||
}
|
||||
Some(cursor_position)
|
||||
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
||||
{
|
||||
|
|
@ -2022,6 +2052,8 @@ struct State {
|
|||
last_notified: Option<Viewport>,
|
||||
last_scrolled: Option<Instant>,
|
||||
is_scrollbar_visible: bool,
|
||||
touch_press_start: Option<Point>,
|
||||
suppress_touch_hover: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -2047,6 +2079,8 @@ impl Default for State {
|
|||
last_notified: None,
|
||||
last_scrolled: None,
|
||||
is_scrollbar_visible: true,
|
||||
touch_press_start: None,
|
||||
suppress_touch_hover: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2349,13 +2383,16 @@ impl Scrollbars {
|
|||
let total_scrollbar_width =
|
||||
width.max(scroller_width) + 2.0 * margin;
|
||||
|
||||
// The padding is purely visual: it shrinks the top and bottom of the
|
||||
// scrollbar without affecting the layout
|
||||
let scrollbar_height = (bounds.height - x_scrollbar_height - 2.0 * padding).max(0.0);
|
||||
|
||||
// Total bounds of the scrollbar + margin + scroller width
|
||||
let total_scrollbar_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - total_scrollbar_width,
|
||||
y: bounds.y + padding,
|
||||
width: total_scrollbar_width,
|
||||
height: (bounds.height - x_scrollbar_height - 2.0 * padding)
|
||||
.max(0.0),
|
||||
height: scrollbar_height,
|
||||
};
|
||||
|
||||
// Bounds of just the scrollbar
|
||||
|
|
@ -2365,8 +2402,7 @@ impl Scrollbars {
|
|||
- width / 2.0,
|
||||
y: bounds.y + padding,
|
||||
width,
|
||||
height: (bounds.height - x_scrollbar_height - 2.0 * padding)
|
||||
.max(0.0),
|
||||
height: scrollbar_height,
|
||||
};
|
||||
|
||||
let ratio = bounds.height / content_bounds.height;
|
||||
|
|
@ -2423,12 +2459,15 @@ impl Scrollbars {
|
|||
let total_scrollbar_height =
|
||||
width.max(scroller_width) + 2.0 * margin;
|
||||
|
||||
// The padding is purely visual: it shrinks the left and right ends of
|
||||
// the scrollbar without affecting the layout
|
||||
let scrollbar_width = (bounds.width - scrollbar_y_width - 2.0 * padding).max(0.0);
|
||||
|
||||
// Total bounds of the scrollbar + margin + scroller width
|
||||
let total_scrollbar_bounds = Rectangle {
|
||||
x: bounds.x + padding,
|
||||
y: bounds.y + bounds.height - total_scrollbar_height,
|
||||
width: (bounds.width - scrollbar_y_width - 2.0 * padding)
|
||||
.max(0.0),
|
||||
width: scrollbar_width,
|
||||
height: total_scrollbar_height,
|
||||
};
|
||||
|
||||
|
|
@ -2438,8 +2477,7 @@ impl Scrollbars {
|
|||
y: bounds.y + bounds.height
|
||||
- total_scrollbar_height / 2.0
|
||||
- width / 2.0,
|
||||
width: (bounds.width - scrollbar_y_width - 2.0 * padding)
|
||||
.max(0.0),
|
||||
width: scrollbar_width,
|
||||
height: width,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ where
|
|||
height: f32,
|
||||
class: Theme::Class<'a>,
|
||||
status: Option<Status>,
|
||||
handle_width: f32,
|
||||
handle_height: f32,
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
|
||||
|
|
@ -119,7 +121,13 @@ where
|
|||
Theme: Catalog,
|
||||
{
|
||||
/// The default height of a [`Slider`].
|
||||
pub const DEFAULT_HEIGHT: f32 = 16.0;
|
||||
pub const DEFAULT_HEIGHT: f32 = 26.0;
|
||||
|
||||
/// The default max height of a [`Handle`].
|
||||
pub const DEFAULT_MAX_HANDLE_HEIGHT: f32 = 26.0;
|
||||
|
||||
/// The default max width of a [`Handle`].
|
||||
pub const DEFAULT_MAX_HANDLE_WIDTH: f32 = 26.0;
|
||||
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
|
|
@ -165,6 +173,8 @@ where
|
|||
height: Self::DEFAULT_HEIGHT,
|
||||
class: Theme::default(),
|
||||
status: None,
|
||||
handle_width: Self::DEFAULT_MAX_HANDLE_WIDTH,
|
||||
handle_height: Self::DEFAULT_MAX_HANDLE_HEIGHT,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +217,24 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the max width of the [`Handle`].
|
||||
///
|
||||
/// Theme can be used to change the size and shape of handle
|
||||
/// but width of handle defined in theme will not exceed this width
|
||||
pub fn handle_width(mut self, width: impl Into<Pixels>) -> Self {
|
||||
self.handle_width = width.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the max height of the [`Handle`].
|
||||
///
|
||||
/// Theme can be used to change the size and shape of handle
|
||||
/// but height of handle defined in theme will not exceed this height
|
||||
pub fn handle_height(mut self, height: impl Into<Pixels>) -> Self {
|
||||
self.handle_height = height.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the step size of the [`Slider`].
|
||||
pub fn step(mut self, step: impl Into<T>) -> Self {
|
||||
self.step = step.into();
|
||||
|
|
@ -316,15 +344,22 @@ where
|
|||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
let mut update = || {
|
||||
let current_value = self.value;
|
||||
|
||||
self.handle_width = self.handle_width.min(layout.bounds().width);
|
||||
self.handle_height = self.handle_height.min(layout.bounds().height);
|
||||
|
||||
let locate = |cursor_position: Point| -> Option<T> {
|
||||
let bounds = layout.bounds();
|
||||
let bounds = Rectangle {
|
||||
x: layout.bounds().x + self.handle_width / 2.0,
|
||||
width: layout.bounds().width - self.handle_width,
|
||||
..layout.bounds()
|
||||
};
|
||||
|
||||
if cursor_position.x <= bounds.x {
|
||||
Some(*self.range.start())
|
||||
|
|
@ -417,6 +452,8 @@ where
|
|||
Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Left,
|
||||
))
|
||||
| Event::Mouse(mouse::Event::CursorLeft)
|
||||
| Event::Mouse(mouse::Event::CursorEntered)
|
||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||
if state.is_dragging {
|
||||
|
|
@ -429,13 +466,20 @@ where
|
|||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||
if state.is_dragging {
|
||||
let _ = cursor
|
||||
.land()
|
||||
.position()
|
||||
.and_then(locate)
|
||||
.map(change);
|
||||
let position = cursor.land().position();
|
||||
let outside = position.is_none_or(|position| {
|
||||
!viewport.contains(position)
|
||||
});
|
||||
|
||||
shell.capture_event();
|
||||
if outside {
|
||||
if let Some(on_release) = self.on_release.clone() {
|
||||
shell.publish(on_release);
|
||||
}
|
||||
state.is_dragging = false;
|
||||
} else {
|
||||
let _ = position.and_then(locate).map(change);
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||
|
|
@ -509,54 +553,30 @@ where
|
|||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let bounds = Rectangle {
|
||||
x: layout.bounds().x + self.handle_width / 2.0,
|
||||
width: layout.bounds().width - self.handle_width,
|
||||
..layout.bounds()
|
||||
};
|
||||
|
||||
let style =
|
||||
theme.style(&self.class, self.status.unwrap_or(Status::Active));
|
||||
|
||||
let border_width = style
|
||||
.handle
|
||||
.border_width
|
||||
.min(bounds.height / 2.0)
|
||||
.min(bounds.width / 2.0);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) =
|
||||
let (mut handle_width, mut handle_height, handle_border_radius) =
|
||||
match style.handle.shape {
|
||||
HandleShape::Circle { radius } => {
|
||||
let radius = (radius)
|
||||
.max(2.0 * border_width)
|
||||
.min(bounds.height / 2.0)
|
||||
.min(bounds.width / 2.0 + 2.0 * border_width);
|
||||
(radius * 2.0, radius * 2.0, Radius::from(radius))
|
||||
}
|
||||
HandleShape::Rectangle {
|
||||
height,
|
||||
width,
|
||||
border_radius,
|
||||
} => {
|
||||
let width = (f32::from(width)).max(2.0 * border_width);
|
||||
let height = (f32::from(height)).max(2.0 * border_width);
|
||||
let mut border_radius: [f32; 4] = border_radius.into();
|
||||
for r in &mut border_radius {
|
||||
*r = (*r)
|
||||
.min(height / 2.0)
|
||||
.min(width / 2.0)
|
||||
.max(*r * (width + border_width * 2.0) / width);
|
||||
}
|
||||
|
||||
(
|
||||
width,
|
||||
height,
|
||||
Radius {
|
||||
top_left: border_radius[0],
|
||||
top_right: border_radius[1],
|
||||
bottom_right: border_radius[2],
|
||||
bottom_left: border_radius[3],
|
||||
},
|
||||
)
|
||||
}
|
||||
} => (f32::from(width), f32::from(height), border_radius),
|
||||
};
|
||||
|
||||
handle_width = handle_width.min(self.handle_width);
|
||||
handle_height = handle_height.min(self.handle_height);
|
||||
|
||||
let value = self.value.into() as f32;
|
||||
let (range_start, range_end) = {
|
||||
let (start, end) = self.range.clone().into_inner();
|
||||
|
|
@ -567,8 +587,7 @@ where
|
|||
let offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.width - handle_width) * (value - range_start)
|
||||
/ (range_end - range_start)
|
||||
bounds.width * (value - range_start) / (range_end - range_start)
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + bounds.height / 2.0;
|
||||
|
|
@ -590,7 +609,8 @@ where
|
|||
x: bounds.x + offset,
|
||||
y: rail_y + 6.0,
|
||||
width: BREAKPOINT_WIDTH,
|
||||
height: 8.0,
|
||||
height: 8.0_f32
|
||||
.min(bounds.y + bounds.height - (rail_y + 6.0)),
|
||||
},
|
||||
border: Border {
|
||||
radius: 0.0.into(),
|
||||
|
|
@ -607,9 +627,9 @@ where
|
|||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: offset + handle_width / 2.0,
|
||||
height: style.rail.width,
|
||||
y: rail_y - (style.rail.width).min(bounds.height) / 2.0,
|
||||
width: offset,
|
||||
height: (style.rail.width).min(bounds.height),
|
||||
},
|
||||
border: style.rail.border,
|
||||
..renderer::Quad::default()
|
||||
|
|
@ -622,10 +642,10 @@ where
|
|||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset + handle_width / 2.0,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: bounds.width - offset - handle_width / 2.0,
|
||||
height: style.rail.width,
|
||||
x: bounds.x + offset,
|
||||
y: rail_y - (style.rail.width).min(bounds.height) / 2.0,
|
||||
width: bounds.width - offset,
|
||||
height: (style.rail.width).min(bounds.height),
|
||||
},
|
||||
border: style.rail.border,
|
||||
..renderer::Quad::default()
|
||||
|
|
@ -637,7 +657,7 @@ where
|
|||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset,
|
||||
x: bounds.x + offset - handle_width / 2.0,
|
||||
y: rail_y - (handle_height / 2.0),
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
//! Display content on top of other content.
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
|
|
|
|||
|
|
@ -179,7 +179,10 @@ where
|
|||
{
|
||||
let color = color.map(Into::into);
|
||||
|
||||
self.style(move |_theme| Style { color })
|
||||
self.style(move |_theme| Style {
|
||||
color,
|
||||
..Style::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the default style class of the [`Rich`] text.
|
||||
|
|
|
|||
|
|
@ -544,6 +544,9 @@ pub struct State<Highlighter: text::Highlighter> {
|
|||
highlighter: RefCell<Highlighter>,
|
||||
highlighter_settings: Highlighter::Settings,
|
||||
highlighter_format_address: usize,
|
||||
context_menu_position: Option<Point>,
|
||||
clipboard_has_text: bool,
|
||||
pending_edit: Option<Action>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -579,6 +582,14 @@ impl<Highlighter: text::Highlighter> State<Highlighter> {
|
|||
pub fn is_focused(&self) -> bool {
|
||||
self.focus.is_some()
|
||||
}
|
||||
|
||||
/// Clears focus, selection, and all interaction state.
|
||||
pub fn clear_focus(&mut self) {
|
||||
self.focus = None;
|
||||
self.drag_click = None;
|
||||
self.context_menu_position = None;
|
||||
self.pending_edit = Some(Action::ClearSelection);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Highlighter: text::Highlighter> operation::Focusable
|
||||
|
|
@ -593,7 +604,7 @@ impl<Highlighter: text::Highlighter> operation::Focusable
|
|||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
self.focus = None;
|
||||
self.clear_focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -621,6 +632,9 @@ where
|
|||
)),
|
||||
highlighter_settings: self.highlighter_settings.clone(),
|
||||
highlighter_format_address: self.highlighter_format as usize,
|
||||
context_menu_position: None,
|
||||
clipboard_has_text: false,
|
||||
pending_edit: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -699,6 +713,60 @@ where
|
|||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
// Handle context menu and focus-loss on clicks outside.
|
||||
{
|
||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||
let text_bounds = layout.bounds();
|
||||
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(_))
|
||||
| Event::Touch(
|
||||
crate::core::touch::Event::FingerPressed { .. }
|
||||
)
|
||||
) && cursor.position_over(text_bounds).is_none()
|
||||
{
|
||||
state.clear_focus();
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Right,
|
||||
)) => {
|
||||
if let Some(pos) = cursor.position_over(text_bounds) {
|
||||
if state.focus.is_none() {
|
||||
state.focus = Some(Focus::now());
|
||||
}
|
||||
state.context_menu_position = Some(pos);
|
||||
state.clipboard_has_text =
|
||||
widget::text::clipboard_has_text(clipboard);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Left,
|
||||
)) => {
|
||||
if state.context_menu_position.take().is_some() {
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Process pending edits from context menu
|
||||
// publishes via on_edit so the app calls content.perform().
|
||||
{
|
||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||
if let Some(action) = state.pending_edit.take() {
|
||||
if let Some(on_edit) = self.on_edit.as_ref() {
|
||||
shell.publish((on_edit)(action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(on_edit) = self.on_edit.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1223,6 +1291,23 @@ impl<Message> Binding<Message> {
|
|||
return Some(binding);
|
||||
}
|
||||
|
||||
let alt_clipboard = match key.as_ref() {
|
||||
keyboard::Key::Named(key::Named::Insert) if modifiers.shift() => {
|
||||
Some(Self::Paste)
|
||||
}
|
||||
keyboard::Key::Named(key::Named::Insert) if modifiers.command() => {
|
||||
Some(Self::Copy)
|
||||
}
|
||||
keyboard::Key::Named(key::Named::Delete) if modifiers.shift() => {
|
||||
Some(Self::Cut)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(binding) = alt_clipboard {
|
||||
return Some(binding);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let modified_key =
|
||||
convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
|
||||
|
|
@ -1545,3 +1630,73 @@ pub(crate) fn convert_macos_shortcut(
|
|||
|
||||
Some(keyboard::Key::Named(key))
|
||||
}
|
||||
|
||||
use crate::core::widget::text::HasSelectableText;
|
||||
|
||||
impl<Highlighter, Message, Theme, Renderer> HasSelectableText
|
||||
for TextEditor<'_, Highlighter, Message, Theme, Renderer>
|
||||
where
|
||||
Highlighter: text::Highlighter,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn selected_text(&self, _tree: &widget::Tree) -> Option<String> {
|
||||
self.content.selection()
|
||||
}
|
||||
|
||||
fn select_all(&self, tree: &mut widget::Tree) {
|
||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||
state.pending_edit = Some(Action::SelectAll);
|
||||
}
|
||||
|
||||
fn is_editable(&self) -> bool {
|
||||
self.on_edit.is_some()
|
||||
}
|
||||
|
||||
fn has_text(&self, _tree: &widget::Tree) -> bool {
|
||||
!self.content.is_empty()
|
||||
}
|
||||
|
||||
fn clipboard_has_text(&self, tree: &widget::Tree) -> bool {
|
||||
tree.state
|
||||
.downcast_ref::<State<Highlighter>>()
|
||||
.clipboard_has_text
|
||||
}
|
||||
|
||||
fn is_focused(&self, tree: &widget::Tree) -> bool {
|
||||
tree.state.downcast_ref::<State<Highlighter>>().is_focused()
|
||||
}
|
||||
|
||||
fn context_menu_position(&self, tree: &widget::Tree) -> Option<Point> {
|
||||
tree.state
|
||||
.downcast_ref::<State<Highlighter>>()
|
||||
.context_menu_position
|
||||
}
|
||||
|
||||
fn set_context_menu_position(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
pos: Option<Point>,
|
||||
) {
|
||||
tree.state
|
||||
.downcast_mut::<State<Highlighter>>()
|
||||
.context_menu_position = pos;
|
||||
}
|
||||
|
||||
fn delete_selection(&self, tree: &mut widget::Tree) -> Option<String> {
|
||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||
state.pending_edit = Some(Action::Edit(Edit::Delete));
|
||||
Some(String::new())
|
||||
}
|
||||
|
||||
fn paste_text(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
text: &str,
|
||||
) -> Option<String> {
|
||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||
state.pending_edit =
|
||||
Some(Action::Edit(Edit::Paste(Arc::new(text.to_owned()))));
|
||||
Some(String::new())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::container;
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ use std::borrow::Cow;
|
|||
use iced_runtime::core::border::Radius;
|
||||
|
||||
use crate::core::alignment;
|
||||
use crate::core::border;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::renderer;
|
||||
|
|
@ -483,6 +482,7 @@ where
|
|||
state.raw(),
|
||||
crate::text::Style {
|
||||
color: style.text_color,
|
||||
..crate::text::Style::default()
|
||||
},
|
||||
viewport,
|
||||
);
|
||||
|
|
@ -509,7 +509,7 @@ where
|
|||
|
||||
let space = style.handle_margin;
|
||||
|
||||
let toggler_background_bounds = Rectangle {
|
||||
let _toggler_background_bounds = Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: bounds.width,
|
||||
|
|
@ -529,7 +529,7 @@ where
|
|||
style.background,
|
||||
);
|
||||
|
||||
let padding = (style.padding_ratio * bounds.height).round();
|
||||
let _padding = (style.padding_ratio * bounds.height).round();
|
||||
let toggler_foreground_bounds = Rectangle {
|
||||
x: bounds.x
|
||||
+ if self.is_toggled {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ where
|
|||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let is_dragging = state.is_dragging;
|
||||
|
|
@ -354,6 +354,8 @@ where
|
|||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Mouse(mouse::Event::CursorLeft)
|
||||
| Event::Mouse(mouse::Event::CursorEntered)
|
||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||
if is_dragging {
|
||||
|
|
@ -366,10 +368,20 @@ where
|
|||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||
if is_dragging {
|
||||
let _ =
|
||||
cursor.land().position().and_then(locate).map(change);
|
||||
let position = cursor.land().position();
|
||||
let outside = position.is_none_or(|position| {
|
||||
!viewport.contains(position)
|
||||
});
|
||||
|
||||
shell.capture_event();
|
||||
if outside {
|
||||
if let Some(on_release) = self.on_release.clone() {
|
||||
shell.publish(on_release);
|
||||
}
|
||||
state.is_dragging = false;
|
||||
} else {
|
||||
let _ = position.and_then(locate).map(change);
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||
|
|
|
|||
|
|
@ -14,7 +14,18 @@ keywords.workspace = true
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
wayland = ["winit/wayland", "wayland-csd-adwaita"]
|
||||
wayland = [
|
||||
"winit/wayland",
|
||||
"wayland-csd-adwaita",
|
||||
"wayland-protocols",
|
||||
"raw-window-handle",
|
||||
"iced_runtime/wayland",
|
||||
"wayland-backend",
|
||||
"xkbcommon",
|
||||
"xkbcommon-dl",
|
||||
"xkeysym",
|
||||
"dep:cctk",
|
||||
]
|
||||
default = ["x11", "wayland-dlopen"]
|
||||
debug = ["iced_debug/enable"]
|
||||
sysinfo = ["dep:sysinfo"]
|
||||
|
|
@ -26,17 +37,6 @@ program = []
|
|||
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||
a11y = ["iced_accessibility", "iced_runtime/a11y"]
|
||||
cctk = [
|
||||
"wayland",
|
||||
"wayland-protocols",
|
||||
"raw-window-handle",
|
||||
"iced_runtime/cctk",
|
||||
"wayland-backend",
|
||||
"xkbcommon",
|
||||
"xkbcommon-dl",
|
||||
"xkeysym",
|
||||
"dep:cctk",
|
||||
]
|
||||
# Prevents multiple separate process instances.
|
||||
single-instance = []
|
||||
|
||||
|
|
@ -64,7 +64,8 @@ iced_debug.workspace = true
|
|||
iced_program.workspace = true
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
|
||||
|
||||
mundy.workspace = true
|
||||
mundy.optional = true
|
||||
|
||||
|
|
@ -89,3 +90,6 @@ winapi.workspace = true
|
|||
web-sys.workspace = true
|
||||
web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
|
||||
wasm-bindgen-futures.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
build_helpers = { path = "../build_helpers" }
|
||||
|
|
|
|||
3
winit/build.rs
Normal file
3
winit/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
build_helpers::cfg_aliases_setup();
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::conversion;
|
||||
use crate::core::mouse;
|
||||
use cursor_icon::CursorIcon;
|
||||
use winit::window::ResizeDirection;
|
||||
|
||||
|
|
@ -24,6 +26,9 @@ const DRAG_RESIZE_SUPPORTED: bool = true;
|
|||
const DRAG_RESIZE_SUPPORTED: bool = false;
|
||||
|
||||
/// If supported by winit, returns a closure that implements cursor resize support.
|
||||
///
|
||||
/// The closure takes the cursor last set by the UI, which it restores when
|
||||
/// the pointer leaves the resize border.
|
||||
pub fn event_func(
|
||||
window: &dyn winit::window::Window,
|
||||
border_size: f64,
|
||||
|
|
@ -32,6 +37,7 @@ pub fn event_func(
|
|||
dyn FnMut(
|
||||
&dyn winit::window::Window,
|
||||
&winit::event::WindowEvent,
|
||||
mouse::Interaction,
|
||||
) -> bool,
|
||||
>,
|
||||
> {
|
||||
|
|
@ -41,7 +47,8 @@ pub fn event_func(
|
|||
|
||||
Some(Box::new(
|
||||
move |window: &dyn winit::window::Window,
|
||||
window_event: &winit::event::WindowEvent|
|
||||
window_event: &winit::event::WindowEvent,
|
||||
ui_interaction: mouse::Interaction|
|
||||
-> bool {
|
||||
// Keep track of border resize state and set cursor icon when in range
|
||||
match window_event {
|
||||
|
|
@ -49,20 +56,30 @@ pub fn event_func(
|
|||
position,
|
||||
..
|
||||
} => {
|
||||
if !window.is_decorated() {
|
||||
let location = cursor_resize_direction(
|
||||
let location = if is_resizable(window) {
|
||||
cursor_resize_direction(
|
||||
window.surface_size(),
|
||||
*position,
|
||||
border_size,
|
||||
);
|
||||
if location != cursor_prev_resize_direction {
|
||||
window.set_cursor(
|
||||
resize_direction_cursor_icon(location)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if location != cursor_prev_resize_direction {
|
||||
match location {
|
||||
Some(direction) => window.set_cursor(
|
||||
resize_direction_cursor_icon(direction)
|
||||
.into(),
|
||||
);
|
||||
cursor_prev_resize_direction = location;
|
||||
return true;
|
||||
),
|
||||
None => restore_cursor(window, ui_interaction),
|
||||
}
|
||||
cursor_prev_resize_direction = location;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
winit::event::WindowEvent::PointerLeft { .. } => {
|
||||
if cursor_prev_resize_direction.take().is_some() {
|
||||
restore_cursor(window, ui_interaction);
|
||||
}
|
||||
}
|
||||
winit::event::WindowEvent::PointerButton {
|
||||
|
|
@ -77,7 +94,9 @@ pub fn event_func(
|
|||
primary: true,
|
||||
..
|
||||
} => {
|
||||
if let Some(direction) = cursor_prev_resize_direction {
|
||||
if let Some(direction) = cursor_prev_resize_direction
|
||||
&& is_resizable(window)
|
||||
{
|
||||
let _res = window.drag_resize_window(direction);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -93,22 +112,37 @@ pub fn event_func(
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether the window can currently be resized from its border.
|
||||
fn is_resizable(window: &dyn winit::window::Window) -> bool {
|
||||
!window.is_decorated()
|
||||
&& !window.is_maximized()
|
||||
&& window.fullscreen().is_none()
|
||||
}
|
||||
|
||||
/// Set the cursor back to the one the UI last set.
|
||||
fn restore_cursor(
|
||||
window: &dyn winit::window::Window,
|
||||
ui_interaction: mouse::Interaction,
|
||||
) {
|
||||
// `None` means the UI hid the cursor, which a cursor icon doesn't undo.
|
||||
if let Some(icon) = conversion::mouse_interaction(ui_interaction) {
|
||||
window.set_cursor(icon.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the cursor icon that corresponds to the resize direction.
|
||||
fn resize_direction_cursor_icon(
|
||||
resize_direction: Option<ResizeDirection>,
|
||||
resize_direction: ResizeDirection,
|
||||
) -> CursorIcon {
|
||||
match resize_direction {
|
||||
Some(resize_direction) => match resize_direction {
|
||||
ResizeDirection::East => CursorIcon::EResize,
|
||||
ResizeDirection::North => CursorIcon::NResize,
|
||||
ResizeDirection::NorthEast => CursorIcon::NeResize,
|
||||
ResizeDirection::NorthWest => CursorIcon::NwResize,
|
||||
ResizeDirection::South => CursorIcon::SResize,
|
||||
ResizeDirection::SouthEast => CursorIcon::SeResize,
|
||||
ResizeDirection::SouthWest => CursorIcon::SwResize,
|
||||
ResizeDirection::West => CursorIcon::WResize,
|
||||
},
|
||||
None => CursorIcon::Default,
|
||||
ResizeDirection::East => CursorIcon::EResize,
|
||||
ResizeDirection::North => CursorIcon::NResize,
|
||||
ResizeDirection::NorthEast => CursorIcon::NeResize,
|
||||
ResizeDirection::NorthWest => CursorIcon::NwResize,
|
||||
ResizeDirection::South => CursorIcon::SResize,
|
||||
ResizeDirection::SouthEast => CursorIcon::SeResize,
|
||||
ResizeDirection::SouthWest => CursorIcon::SwResize,
|
||||
ResizeDirection::West => CursorIcon::WResize,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ impl Clipboard {
|
|||
})
|
||||
.unwrap_or(State::Unavailable);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(wayland_platform)]
|
||||
if let State::Connected { clipboard, .. } = &state {
|
||||
clipboard.init_dnd(Box::new(sender));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,33 @@ use winit::event::Force;
|
|||
use winit::icon::IconProvider;
|
||||
use winit::keyboard::SmolStr;
|
||||
|
||||
/// Returns the activation token provided by the compositor, if any.
|
||||
///
|
||||
/// The token is single-use, so it is only ever handed out to the first window.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn take_activation_token() -> Option<winit::window::ActivationToken> {
|
||||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
use winit::platform::startup_notify::reset_activation_token_env;
|
||||
|
||||
static TOKEN: OnceLock<Mutex<Option<winit::window::ActivationToken>>> =
|
||||
OnceLock::new();
|
||||
|
||||
TOKEN
|
||||
.get_or_init(|| {
|
||||
let token = std::env::var("XDG_ACTIVATION_TOKEN")
|
||||
.ok()
|
||||
.map(winit::window::ActivationToken::from_raw);
|
||||
|
||||
reset_activation_token_env();
|
||||
|
||||
Mutex::new(token)
|
||||
})
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut token| token.take())
|
||||
}
|
||||
|
||||
/// Converts some [`window::Settings`] into some `WindowAttributes` from `winit`.
|
||||
pub fn window_attributes(
|
||||
settings: window::Settings,
|
||||
|
|
@ -182,12 +209,19 @@ pub fn window_attributes(
|
|||
{
|
||||
use winit::platform::wayland::WindowAttributesWayland;
|
||||
|
||||
attributes = attributes.with_platform_attributes(Box::new(
|
||||
WindowAttributesWayland::default().with_name(
|
||||
let mut wayland_attributes = WindowAttributesWayland::default()
|
||||
.with_name(
|
||||
&settings.platform_specific.application_id,
|
||||
&settings.platform_specific.application_id,
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
if let Some(token) = take_activation_token() {
|
||||
wayland_attributes =
|
||||
wayland_attributes.with_activation_token(token);
|
||||
}
|
||||
|
||||
attributes = attributes
|
||||
.with_platform_attributes(Box::new(wayland_attributes));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +269,13 @@ pub fn window_event(
|
|||
scale_factor,
|
||||
)))
|
||||
}
|
||||
winit::event::PointerSource::TabletTool { .. } => {
|
||||
let position = position.to_logical::<f64>(scale_factor);
|
||||
|
||||
Some(Event::Mouse(mouse::Event::CursorMoved {
|
||||
position: Point::new(position.x as f32, position.y as f32),
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
// TODO how to handle this for Touch? Is it different than a press? Need to double check winit impl
|
||||
|
|
@ -272,6 +313,24 @@ pub fn window_event(
|
|||
}
|
||||
}))
|
||||
}
|
||||
WindowEvent::PointerButton {
|
||||
button: ButtonSource::TabletTool { button, .. },
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
let button: Option<winit::event::MouseButton> = button.into();
|
||||
|
||||
button.map(mouse_button).map(|button| {
|
||||
Event::Mouse(match state {
|
||||
winit::event::ElementState::Pressed => {
|
||||
mouse::Event::ButtonPressed(button)
|
||||
}
|
||||
winit::event::ElementState::Released => {
|
||||
mouse::Event::ButtonReleased(button)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
WindowEvent::PointerButton {
|
||||
button: ButtonSource::Touch { finger_id, force },
|
||||
state,
|
||||
|
|
@ -387,19 +446,19 @@ pub fn window_event(
|
|||
self::modifiers(new_modifiers.state()),
|
||||
)))
|
||||
}
|
||||
WindowEvent::Ime(event) => Some(Event::InputMethod(match event {
|
||||
Ime::Enabled => input_method::Event::Opened,
|
||||
Ime::Preedit(content, size) => input_method::Event::Preedit(
|
||||
WindowEvent::Ime(event) => match event {
|
||||
Ime::Enabled => Some(input_method::Event::Opened),
|
||||
Ime::Preedit(content, size) => Some(input_method::Event::Preedit(
|
||||
content,
|
||||
size.map(|(start, end)| start..end),
|
||||
),
|
||||
Ime::Commit(content) => input_method::Event::Commit(content),
|
||||
Ime::Disabled => input_method::Event::Closed,
|
||||
Ime::DeleteSurrounding {
|
||||
before_bytes,
|
||||
after_bytes,
|
||||
} => todo!(),
|
||||
})),
|
||||
)),
|
||||
Ime::Commit(content) => Some(input_method::Event::Commit(content)),
|
||||
Ime::Disabled => Some(input_method::Event::Closed),
|
||||
// Upstream iced depends on the winit version which doesn't support
|
||||
// this kind of ime event.
|
||||
Ime::DeleteSurrounding { .. } => None,
|
||||
}
|
||||
.map(Event::InputMethod),
|
||||
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
|
||||
window::Event::Focused
|
||||
} else {
|
||||
|
|
@ -424,7 +483,7 @@ pub fn window_event(
|
|||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
Some(Event::Window(window::Event::Rescaled(scale_factor as f32)))
|
||||
}
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
WindowEvent::SuggestedBounds(bounds) => {
|
||||
let size = bounds.map(|bounds| {
|
||||
let size = bounds.to_logical(scale_factor);
|
||||
|
|
@ -437,7 +496,7 @@ pub fn window_event(
|
|||
),
|
||||
)))
|
||||
}
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
WindowEvent::WindowStateChanged => {
|
||||
use cctk::sctk::reexports::csd_frame::WindowState;
|
||||
use winit::platform::wayland::WindowExtWayland;
|
||||
|
|
@ -1491,7 +1550,7 @@ pub fn winit_key_code(
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
fn winit_native_key_code(
|
||||
keycode: keyboard::key::NativeCode,
|
||||
) -> winit::keyboard::NativeKeyCode {
|
||||
|
|
@ -1515,7 +1574,7 @@ fn winit_native_key_code(
|
|||
}
|
||||
|
||||
/// Reconstruct the raw keycode
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn physical_to_scancode(physical: keyboard::key::Physical) -> Option<u32> {
|
||||
let Some(physical_key) = (match physical {
|
||||
keyboard::key::Physical::Code(code) => {
|
||||
|
|
|
|||
291
winit/src/lib.rs
291
winit/src/lib.rs
|
|
@ -40,12 +40,6 @@ pub mod clipboard;
|
|||
pub mod conversion;
|
||||
pub mod platform_specific;
|
||||
|
||||
#[cfg(feature = "program")]
|
||||
pub mod program;
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub mod system;
|
||||
|
||||
mod error;
|
||||
mod proxy;
|
||||
mod window;
|
||||
|
|
@ -53,7 +47,6 @@ mod window;
|
|||
pub use clipboard::Clipboard;
|
||||
pub use error::Error;
|
||||
pub use proxy::Proxy;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::dpi::PhysicalPosition;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
|
|
@ -76,6 +69,8 @@ use crate::runtime::user_interface::{self, UserInterface};
|
|||
use crate::runtime::{Action, Task};
|
||||
|
||||
use program::Program;
|
||||
#[cfg(feature = "program")]
|
||||
pub use program::Program;
|
||||
use window::WindowManager;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
|
@ -98,10 +93,10 @@ where
|
|||
|
||||
let event_loop = EventLoop::new().expect("Create event loop");
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
let is_wayland =
|
||||
winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop);
|
||||
#[cfg(not(all(feature = "cctk", target_os = "linux")))]
|
||||
#[cfg(not(wayland_platform))]
|
||||
let is_wayland = false;
|
||||
|
||||
// TODO this is new..
|
||||
|
|
@ -192,10 +187,14 @@ where
|
|||
receiver: mpsc::UnboundedReceiver<Control>,
|
||||
error: Option<Error>,
|
||||
system_theme: Option<oneshot::Sender<theme::Mode>>,
|
||||
#[cfg(feature = "a11y")]
|
||||
control_sender: mpsc::UnboundedSender<Control>,
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
adapters: std::collections::HashMap<window::Id, (u64, iced_accessibility::accesskit_winit::Adapter)>,
|
||||
adapters: std::collections::HashMap<
|
||||
window::Id,
|
||||
(u64, iced_accessibility::accesskit_winit::Adapter),
|
||||
>,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
is_booted: std::rc::Rc<std::cell::RefCell<bool>>,
|
||||
|
|
@ -215,6 +214,7 @@ where
|
|||
id: settings.id,
|
||||
sender: event_sender,
|
||||
receiver: control_receiver,
|
||||
#[cfg(feature = "a11y")]
|
||||
control_sender: control_sender.clone(),
|
||||
error: None,
|
||||
system_theme: Some(system_theme_sender),
|
||||
|
|
@ -263,7 +263,7 @@ where
|
|||
| winit::event::WindowEvent::Moved(_)
|
||||
);
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
if matches!(event, WindowEvent::RedrawRequested) {
|
||||
for id in
|
||||
|
|
@ -501,9 +501,15 @@ where
|
|||
}
|
||||
};
|
||||
}
|
||||
let window: Arc<dyn winit::window::Window + 'static> = Arc::from(window);
|
||||
let window: Arc<
|
||||
dyn winit::window::Window + 'static,
|
||||
> = Arc::from(window);
|
||||
#[cfg(feature = "a11y")]
|
||||
self.init_adapter(event_loop, id, window.clone());
|
||||
self.init_adapter(
|
||||
event_loop,
|
||||
id,
|
||||
window.clone(),
|
||||
);
|
||||
|
||||
self.process_event(
|
||||
event_loop,
|
||||
|
|
@ -572,11 +578,10 @@ where
|
|||
.expect("Send event");
|
||||
}
|
||||
Control::Winit(id, e) => {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
if matches!(e, WindowEvent::RedrawRequested)
|
||||
{
|
||||
|
||||
for id in crate::subsurface_widget::subsurface_ids(id) {
|
||||
_ = self.sender
|
||||
.unbounded_send(Event::Winit(
|
||||
|
|
@ -618,8 +623,8 @@ where
|
|||
}
|
||||
#[cfg(feature = "a11y")]
|
||||
Control::Cleanup(id) => {
|
||||
_ = self.adapters.remove(&id);
|
||||
},
|
||||
_ = self.adapters.remove(&id);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
break;
|
||||
|
|
@ -633,33 +638,34 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
fn init_adapter(&mut self, event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static), id: core::window::Id, window: Arc<dyn winit::window::Window + 'static>) {
|
||||
#[cfg(feature = "a11y")]
|
||||
fn init_adapter(
|
||||
&mut self,
|
||||
event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static),
|
||||
id: core::window::Id,
|
||||
window: Arc<dyn winit::window::Window + 'static>,
|
||||
) {
|
||||
use crate::a11y::*;
|
||||
use iced_accessibility::accesskit::{
|
||||
ActivationHandler, Node, NodeId, Role,
|
||||
Tree, TreeUpdate,
|
||||
ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
|
||||
};
|
||||
use iced_accessibility::accesskit_winit::Adapter;
|
||||
|
||||
let node_id =
|
||||
iced_runtime::core::id::window_node_id();
|
||||
let node_id = iced_runtime::core::id::window_node_id();
|
||||
|
||||
let activation_handler =
|
||||
WinitActivationHandler {
|
||||
proxy: self.control_sender.clone(),
|
||||
title: String::new(),
|
||||
};
|
||||
let activation_handler = WinitActivationHandler {
|
||||
proxy: self.control_sender.clone(),
|
||||
title: String::new(),
|
||||
};
|
||||
|
||||
let action_handler = WinitActionHandler {
|
||||
id,
|
||||
proxy: self.control_sender.clone(),
|
||||
};
|
||||
|
||||
let deactivation_handler =
|
||||
WinitDeactivationHandler {
|
||||
proxy: self.control_sender.clone(),
|
||||
};
|
||||
let deactivation_handler = WinitDeactivationHandler {
|
||||
proxy: self.control_sender.clone(),
|
||||
};
|
||||
|
||||
_ = self.adapters.insert(
|
||||
id,
|
||||
|
|
@ -674,8 +680,6 @@ where
|
|||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -770,7 +774,7 @@ async fn run_instance<P>(
|
|||
|
||||
let mut platform_specific_handler =
|
||||
crate::platform_specific::PlatformSpecific::default();
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
if is_wayland {
|
||||
platform_specific_handler = platform_specific_handler.with_wayland(
|
||||
control_sender.clone(),
|
||||
|
|
@ -838,6 +842,30 @@ async fn run_instance<P>(
|
|||
|
||||
log::info!("System theme: {system_theme:?}");
|
||||
|
||||
macro_rules! run_action {
|
||||
($action:expr) => {
|
||||
run_action(
|
||||
$action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
&mut clipboard,
|
||||
&mut control_sender,
|
||||
&mut user_interfaces,
|
||||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
&mut platform_specific_handler,
|
||||
&mut dnd_surface,
|
||||
&mut dnd_surface_id,
|
||||
is_daemon,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
'next_event: loop {
|
||||
// Empty the queue if possible
|
||||
let event = if let Ok(event) = event_receiver.try_recv() {
|
||||
|
|
@ -857,35 +885,36 @@ async fn run_instance<P>(
|
|||
exit_on_close_request,
|
||||
make_visible,
|
||||
on_open,
|
||||
resize_border
|
||||
resize_border,
|
||||
} => {
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
platform_specific_handler.send_wayland(
|
||||
platform_specific::Action::TrackWindow(window.clone(), id),
|
||||
);
|
||||
match create_compositor::<P>(
|
||||
window.clone(),
|
||||
CreateCompositor {
|
||||
proxy: &proxy,
|
||||
runtime: &mut runtime,
|
||||
display_handle: &display_handle,
|
||||
graphics_settings: &graphics_settings,
|
||||
default_fonts: &default_fonts,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(comp) => {
|
||||
compositor = Some(comp);
|
||||
}
|
||||
Err(error) => {
|
||||
control_sender
|
||||
.start_send(Control::Crash(
|
||||
Error::GraphicsCreationFailed(error),
|
||||
))
|
||||
.expect("Send control message");
|
||||
continue;
|
||||
if compositor.is_none() {
|
||||
match create_compositor::<P>(
|
||||
window.clone(),
|
||||
CreateCompositor {
|
||||
proxy: &proxy,
|
||||
runtime: &mut runtime,
|
||||
display_handle: &display_handle,
|
||||
graphics_settings: &graphics_settings,
|
||||
default_fonts: &default_fonts,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(comp) => {
|
||||
compositor = Some(comp);
|
||||
}
|
||||
Err(error) => {
|
||||
control_sender
|
||||
.start_send(Control::Crash(
|
||||
Error::GraphicsCreationFailed(error),
|
||||
))
|
||||
.expect("Send control message");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -913,7 +942,7 @@ async fn run_instance<P>(
|
|||
compositor,
|
||||
exit_on_close_request,
|
||||
system_theme,
|
||||
resize_border
|
||||
resize_border,
|
||||
);
|
||||
|
||||
window.raw.set_theme(conversion::window_theme(
|
||||
|
|
@ -999,22 +1028,7 @@ async fn run_instance<P>(
|
|||
}
|
||||
}
|
||||
Event::UserEvent(action) => {
|
||||
let exited = run_action(
|
||||
action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
&mut clipboard,
|
||||
&mut control_sender,
|
||||
&mut user_interfaces,
|
||||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
&mut platform_specific_handler,
|
||||
);
|
||||
let exited = run_action!(action);
|
||||
if exited {
|
||||
runtime.track(None.into_iter());
|
||||
}
|
||||
|
|
@ -1025,7 +1039,7 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
};
|
||||
|
||||
let Some((id, mut window)) =
|
||||
let Some((id, mut window)) =
|
||||
window_manager.get_mut_alias(window_id)
|
||||
else {
|
||||
continue;
|
||||
|
|
@ -1042,10 +1056,10 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
}
|
||||
// XX must force update to corner radius before the surface is committed.
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
if (window.surface_version != window.state.surface_version()
|
||||
|| window.logical_size() != window.state.logical_size()
|
||||
) && !crate::subsurface_widget::is_subsurface(window_id)
|
||||
|| window.logical_size() != window.state.logical_size())
|
||||
&& !crate::subsurface_widget::is_subsurface(window_id)
|
||||
{
|
||||
platform_specific_handler.send_wayland(
|
||||
platform_specific::Action::ResizeWindow(id),
|
||||
|
|
@ -1156,22 +1170,7 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
}
|
||||
|
||||
_ = run_action(
|
||||
action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
&mut clipboard,
|
||||
&mut control_sender,
|
||||
&mut user_interfaces,
|
||||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
&mut platform_specific_handler,
|
||||
);
|
||||
_ = run_action!(action);
|
||||
}
|
||||
|
||||
for (window_id, window) in window_manager.iter_mut() {
|
||||
|
|
@ -1326,13 +1325,26 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
};
|
||||
// Initiates a drag resize window state when found.
|
||||
if let Some(func) =
|
||||
window.drag_resize_window_func.as_mut()
|
||||
{
|
||||
if func(window.raw.as_ref(), &event) {
|
||||
if let Some(func) = window.drag_resize_window_func.as_mut() {
|
||||
if func(
|
||||
window.raw.as_ref(),
|
||||
&event,
|
||||
window.mouse_interaction,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// to avoid race with the reported modifier state from the popup of
|
||||
// this window, don't empty the modifiers, on focus loss
|
||||
#[cfg(wayland_platform)]
|
||||
if let winit::event::WindowEvent::ModifiersChanged(modifiers) =
|
||||
&event
|
||||
&& modifiers.state().is_empty()
|
||||
&& platform_specific_handler.has_popup(id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match event {
|
||||
winit::event::WindowEvent::SurfaceResized(_) => {
|
||||
window.raw.request_redraw();
|
||||
|
|
@ -1354,22 +1366,9 @@ async fn run_instance<P>(
|
|||
if matches!(event, winit::event::WindowEvent::CloseRequested)
|
||||
&& window.exit_on_close_request
|
||||
{
|
||||
_ = run_action(
|
||||
Action::Window(runtime::window::Action::Close(id)),
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
&mut clipboard,
|
||||
&mut control_sender,
|
||||
&mut user_interfaces,
|
||||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
&mut platform_specific_handler,
|
||||
);
|
||||
_ = run_action!(Action::Window(
|
||||
runtime::window::Action::Close(id)
|
||||
));
|
||||
} else {
|
||||
window.state.update(&program, window.raw.as_ref(), &event);
|
||||
|
||||
|
|
@ -1411,8 +1410,7 @@ async fn run_instance<P>(
|
|||
true
|
||||
}
|
||||
});
|
||||
let no_window_events = window_events.is_empty();
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
window_events.push(core::Event::PlatformSpecific(
|
||||
core::event::PlatformSpecific::Wayland(
|
||||
core::event::wayland::Event::RequestResize,
|
||||
|
|
@ -1429,8 +1427,7 @@ async fn run_instance<P>(
|
|||
&mut clipboard,
|
||||
&mut messages,
|
||||
);
|
||||
let mut needs_redraw =
|
||||
!no_window_events || !messages.is_empty();
|
||||
let mut needs_redraw = !messages.is_empty();
|
||||
if let Some(requested_size) =
|
||||
clipboard.requested_logical_size.lock().unwrap().take()
|
||||
{
|
||||
|
|
@ -1486,8 +1483,17 @@ async fn run_instance<P>(
|
|||
mouse_interaction,
|
||||
..
|
||||
} => {
|
||||
let mouse_changed =
|
||||
window.mouse_interaction != mouse_interaction;
|
||||
|
||||
window.update_mouse(mouse_interaction);
|
||||
|
||||
if mouse_changed {
|
||||
window.request_redraw(
|
||||
core::window::RedrawRequest::NextFrame,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unconditional-rendering"))]
|
||||
window.request_redraw(_redraw_request);
|
||||
}
|
||||
|
|
@ -1547,22 +1553,7 @@ async fn run_instance<P>(
|
|||
));
|
||||
|
||||
for action in actions {
|
||||
let exited = run_action(
|
||||
action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
&mut clipboard,
|
||||
&mut control_sender,
|
||||
&mut user_interfaces,
|
||||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
&mut platform_specific_handler,
|
||||
);
|
||||
let exited = run_action!(action);
|
||||
if exited {
|
||||
runtime.track(None.into_iter());
|
||||
}
|
||||
|
|
@ -1772,7 +1763,6 @@ async fn run_instance<P>(
|
|||
};
|
||||
|
||||
// search windows for widget with operation
|
||||
|
||||
if result.is_none() {
|
||||
log::warn!(
|
||||
"start_dnd: widget {:?} not found; drag will fail",
|
||||
|
|
@ -2106,6 +2096,11 @@ fn run_action<'a, P, C>(
|
|||
is_window_opening: &mut bool,
|
||||
system_theme: &mut theme::Mode,
|
||||
platform_specific: &mut crate::platform_specific::PlatformSpecific,
|
||||
dnd_surface: &mut Option<
|
||||
Arc<Box<dyn HasWindowHandle + Send + Sync + 'static>>,
|
||||
>,
|
||||
dnd_surface_id: &mut Option<window::Id>,
|
||||
is_daemon: bool,
|
||||
) -> bool
|
||||
where
|
||||
P: Program,
|
||||
|
|
@ -2159,7 +2154,7 @@ where
|
|||
_ = control_sender.start_send(Control::Cleanup(id)).ok();
|
||||
}
|
||||
let proxy = clipboard.proxy();
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
platform_specific
|
||||
.send_wayland(platform_specific::Action::RemoveWindow(id));
|
||||
if let Some(window) = window_manager.remove(id) {
|
||||
|
|
@ -2190,7 +2185,7 @@ where
|
|||
));
|
||||
}
|
||||
|
||||
if window_manager.is_empty() {
|
||||
if window_manager.is_empty() && !is_daemon {
|
||||
*compositor = None;
|
||||
}
|
||||
}
|
||||
|
|
@ -2543,6 +2538,8 @@ where
|
|||
while let Some(mut operation) = current_operation.take() {
|
||||
for (id, ui) in interfaces.iter_mut() {
|
||||
if let Some(window) = window_manager.get_mut(*id) {
|
||||
operation.as_mut().set_window_id(*id);
|
||||
|
||||
ui.operate(&window.renderer, operation.as_mut());
|
||||
}
|
||||
}
|
||||
|
|
@ -2620,6 +2617,10 @@ where
|
|||
}
|
||||
iced_runtime::dnd::DndAction::EndDnd => {
|
||||
clipboard.end_dnd();
|
||||
*dnd_surface = None;
|
||||
if let Some(id) = dnd_surface_id.take() {
|
||||
platform_specific.retain_subsurfaces(|other| other != id);
|
||||
}
|
||||
}
|
||||
iced_runtime::dnd::DndAction::PeekDnd(m, channel) => {
|
||||
let data = clipboard.peek_dnd(m);
|
||||
|
|
@ -2657,10 +2658,10 @@ where
|
|||
.and_then(|window| theme::Base::palette(window.state.theme()))
|
||||
});
|
||||
|
||||
cached_user_interfaces
|
||||
.drain()
|
||||
.filter_map(|(id, cache)| {
|
||||
let window = window_manager.get_mut(id)?;
|
||||
window_manager
|
||||
.iter_mut()
|
||||
.filter_map(|(id, window)| {
|
||||
let cache = cached_user_interfaces.remove(&id)?;
|
||||
|
||||
Some((
|
||||
id,
|
||||
|
|
@ -2733,7 +2734,5 @@ fn system_information(
|
|||
graphics_backend: graphics.backend,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "program")]
|
||||
pub use program::Program;
|
||||
|
||||
pub use platform_specific::*;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
//! Wayland specific shell
|
||||
//!
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
use cctk::sctk::reexports::client::Connection;
|
||||
use iced_graphics::{Compositor, compositor};
|
||||
use iced_graphics::compositor;
|
||||
use iced_runtime::{
|
||||
core::{Vector, window},
|
||||
platform_specific, user_interface,
|
||||
user_interface,
|
||||
};
|
||||
use winit::raw_window_handle::HasWindowHandle;
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
pub mod wayland;
|
||||
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
pub use wayland::*;
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
use wayland_backend::client::Backend;
|
||||
|
||||
use crate::{CreateCompositor, Program, WindowManager};
|
||||
|
||||
|
|
@ -35,7 +31,7 @@ pub type UserInterfaces<'a, P> = HashMap<
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(sctk_event::SctkEvent),
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +57,7 @@ impl SurfaceIdWrapper {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PlatformSpecific {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
wayland: WaylandSpecific,
|
||||
}
|
||||
|
||||
|
|
@ -71,25 +67,30 @@ impl PlatformSpecific {
|
|||
action: iced_runtime::platform_specific::Action,
|
||||
) {
|
||||
match action {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
iced_runtime::platform_specific::Action::Wayland(a) => {
|
||||
self.send_wayland(wayland::Action::Action(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
pub(crate) fn has_popup(&self, toplevel: window::Id) -> bool {
|
||||
self.wayland.has_popup(toplevel)
|
||||
}
|
||||
|
||||
pub(crate) fn retain_subsurfaces<F: Fn(window::Id) -> bool>(
|
||||
&mut self,
|
||||
keep: F,
|
||||
) {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
self.wayland.retain_subsurfaces(keep);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear_subsurface_list(&mut self) {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
self.wayland.clear_subsurface_list();
|
||||
}
|
||||
|
|
@ -100,7 +101,7 @@ impl PlatformSpecific {
|
|||
id: window::Id,
|
||||
window: &dyn HasWindowHandle,
|
||||
) {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
use cctk::sctk::reexports::client::{
|
||||
Proxy, protocol::wl_surface::WlSurface,
|
||||
|
|
@ -151,11 +152,14 @@ impl PlatformSpecific {
|
|||
pub(crate) fn create_surface(
|
||||
&mut self,
|
||||
) -> Option<Box<dyn HasWindowHandle + Send + Sync + 'static>> {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
return self.wayland.create_surface();
|
||||
self.wayland.create_surface()
|
||||
}
|
||||
#[cfg(not(wayland_platform))]
|
||||
{
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn update_surface_shm(
|
||||
|
|
@ -167,7 +171,7 @@ impl PlatformSpecific {
|
|||
data: &[u8],
|
||||
offset: Vector,
|
||||
) {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
{
|
||||
return self.wayland.update_surface_shm(
|
||||
surface, width, height, scale, data, offset,
|
||||
|
|
@ -195,7 +199,7 @@ pub(crate) async fn handle_event<'a, 'b, P>(
|
|||
P: Program,
|
||||
{
|
||||
match e {
|
||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
||||
#[cfg(wayland_platform)]
|
||||
Event::Wayland(e) => {
|
||||
platform_specific
|
||||
.wayland
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::window::Id as SurfaceId;
|
||||
use iced_runtime::{
|
||||
self,
|
||||
self, Action, Task,
|
||||
platform_specific::{self, wayland},
|
||||
task, Action, Task,
|
||||
task,
|
||||
};
|
||||
|
||||
pub fn request_token(
|
||||
|
|
|
|||
17
winit/src/platform_specific/wayland/commands/blur.rs
Normal file
17
winit/src/platform_specific/wayland/commands/blur.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use iced_futures::core::window;
|
||||
use iced_runtime::{
|
||||
self, Action, Task,
|
||||
platform_specific::{self, wayland},
|
||||
task,
|
||||
};
|
||||
|
||||
pub fn blur(
|
||||
id: window::Id,
|
||||
blur: Option<Vec<iced_runtime::core::Rectangle>>,
|
||||
) -> Task<()> {
|
||||
task::oneshot(|_| {
|
||||
Action::PlatformSpecific(platform_specific::Action::Wayland(
|
||||
wayland::Action::BlurSurface(id, blur),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
use iced_futures::core::window;
|
||||
use iced_runtime::{
|
||||
self,
|
||||
self, Action, Task,
|
||||
platform_specific::{
|
||||
self,
|
||||
wayland::{self, CornerRadius},
|
||||
},
|
||||
task, Action, Task,
|
||||
task,
|
||||
};
|
||||
|
||||
pub fn corner_radius(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use iced_runtime::{
|
||||
self,
|
||||
self, Action, Task,
|
||||
platform_specific::{self, wayland},
|
||||
task, Action, Task,
|
||||
task,
|
||||
};
|
||||
|
||||
pub fn inhibit_shortcuts(inhibit: bool) -> Task<()> {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! Interact with the window of your application.
|
||||
|
||||
use crate::core::Rectangle;
|
||||
use crate::core::window::Id as SurfaceId;
|
||||
use iced_runtime::{
|
||||
self,
|
||||
self, Action, Task,
|
||||
platform_specific::{
|
||||
self,
|
||||
wayland::{
|
||||
|
|
@ -10,7 +11,7 @@ use iced_runtime::{
|
|||
layer_surface::{IcedMargin, SctkLayerSurfaceSettings},
|
||||
},
|
||||
},
|
||||
task, Action, Task,
|
||||
task,
|
||||
};
|
||||
|
||||
pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
|
||||
|
|
@ -114,3 +115,37 @@ pub fn set_layer<Message>(id: SurfaceId, layer: Layer) -> Task<Message> {
|
|||
)),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_padding<Message>(
|
||||
id: SurfaceId,
|
||||
padding: IcedMargin,
|
||||
) -> Task<Message> {
|
||||
task::effect(Action::PlatformSpecific(
|
||||
platform_specific::Action::Wayland(wayland::Action::LayerSurface(
|
||||
wayland::layer_surface::Action::Padding { id, padding },
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://wayland.app/protocols/wayland#wl_surface:request:set_input_region>
|
||||
pub fn set_input_zone<Message>(
|
||||
id: SurfaceId,
|
||||
zone: Option<Vec<Rectangle>>,
|
||||
) -> Task<Message> {
|
||||
task::effect(Action::PlatformSpecific(
|
||||
platform_specific::Action::Wayland(wayland::Action::LayerSurface(
|
||||
wayland::layer_surface::Action::InputZone { id, zone },
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_show_on_lock<Message>(
|
||||
id: SurfaceId,
|
||||
value: bool,
|
||||
) -> Task<Message> {
|
||||
task::effect(Action::PlatformSpecific(
|
||||
platform_specific::Action::Wayland(wayland::Action::LayerSurface(
|
||||
wayland::layer_surface::Action::ShowOnLock { id, value },
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Interact with the wayland objects of your application.
|
||||
|
||||
pub mod activation;
|
||||
pub mod blur;
|
||||
pub mod corner_radius;
|
||||
pub mod keyboard_shortcuts_inhibit;
|
||||
pub mod layer_surface;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue