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_accessibility?/accesskit_winit",
|
||||||
"iced_program/winit",
|
"iced_program/winit",
|
||||||
]
|
]
|
||||||
wayland = ["iced_renderer/wayland", "iced_winit/wayland"]
|
wayland = [
|
||||||
cctk = ["iced_winit/cctk", "iced_widget/cctk", "iced_core/cctk", "wayland"]
|
"iced_renderer/wayland",
|
||||||
# Enables the sctk shell.
|
"iced_winit/wayland",
|
||||||
|
"iced_runtime/wayland",
|
||||||
|
"iced_widget/wayland",
|
||||||
|
"iced_core/wayland",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
|
||||||
iced_devtools.workspace = true
|
iced_devtools.workspace = true
|
||||||
|
iced_devtools.optional = true
|
||||||
iced_debug.workspace = true
|
iced_debug.workspace = true
|
||||||
|
|
||||||
iced_program.workspace = true
|
iced_program.workspace = true
|
||||||
|
|
@ -134,10 +138,9 @@ iced_renderer.workspace = true
|
||||||
iced_runtime.workspace = true
|
iced_runtime.workspace = true
|
||||||
iced_widget.workspace = true
|
iced_widget.workspace = true
|
||||||
iced_winit.workspace = true
|
iced_winit.workspace = true
|
||||||
iced_devtools.optional = true
|
iced_winit.optional = true
|
||||||
iced_tester.workspace = true
|
iced_tester.workspace = true
|
||||||
iced_tester.optional = true
|
iced_tester.optional = true
|
||||||
iced_winit.optional = true
|
|
||||||
iced_highlighter.workspace = true
|
iced_highlighter.workspace = true
|
||||||
iced_highlighter.optional = true
|
iced_highlighter.optional = true
|
||||||
iced_accessibility.workspace = true
|
iced_accessibility.workspace = true
|
||||||
|
|
@ -190,6 +193,7 @@ members = [
|
||||||
"winit",
|
"winit",
|
||||||
"examples/*",
|
"examples/*",
|
||||||
"accessibility",
|
"accessibility",
|
||||||
|
"build_helpers",
|
||||||
]
|
]
|
||||||
exclude = ["examples/integration"]
|
exclude = ["examples/integration"]
|
||||||
|
|
||||||
|
|
@ -202,7 +206,7 @@ repository = "https://github.com/iced-rs/iced"
|
||||||
homepage = "https://iced.rs"
|
homepage = "https://iced.rs"
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||||
rust-version = "1.89"
|
rust-version = "1.92"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
iced = { version = "0.14.0", path = "." }
|
iced = { version = "0.14.0", path = "." }
|
||||||
|
|
@ -238,10 +242,9 @@ async-std = "1.0"
|
||||||
bitflags = "2.5"
|
bitflags = "2.5"
|
||||||
bytemuck = { version = "1.0", features = ["derive"] }
|
bytemuck = { version = "1.0", features = ["derive"] }
|
||||||
bytes = "1.6"
|
bytes = "1.6"
|
||||||
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git" }
|
cosmic-text = { path = "../../cosmic-text" }
|
||||||
# cosmic-text = "0.10"
|
cryoglyph = { package = "cryoglyph", path = "../../glyphon" }
|
||||||
dark-light = "1.0"
|
dark-light = "1.0"
|
||||||
cryoglyph = { package = "cryoglyph", git = "https://github.com/pop-os/glyphon.git", tag = "cosmic-0.14" }
|
|
||||||
resvg = "0.45"
|
resvg = "0.45"
|
||||||
web-sys = "0.3.69"
|
web-sys = "0.3.69"
|
||||||
guillotiere = "0.6"
|
guillotiere = "0.6"
|
||||||
|
|
@ -275,8 +278,11 @@ tiny-skia = { version = "0.11", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"simd",
|
"simd",
|
||||||
] }
|
] }
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086" }
|
cctk = { path = "../../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit" }
|
||||||
softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" }
|
# 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"
|
syntect = "5.2"
|
||||||
tokio = "1.0"
|
tokio = "1.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
@ -288,7 +294,7 @@ url = "2.5"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
wasmtimer = "0.4.2"
|
wasmtimer = "0.4.2"
|
||||||
web-time = "1.1"
|
web-time = "1.1"
|
||||||
wgpu = { version = "27.0", default-features = false, features = [
|
wgpu = { version = "28.0", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"wgsl",
|
"wgsl",
|
||||||
] }
|
] }
|
||||||
|
|
@ -302,13 +308,40 @@ wayland-client = { version = "0.31.5" }
|
||||||
winapi = "0.3"
|
winapi = "0.3"
|
||||||
# window_clipboard = "0.4.1"
|
# window_clipboard = "0.4.1"
|
||||||
|
|
||||||
window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
# Yoda: window_clipboard pinned to our public fork that has x11 gated behind
|
||||||
dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
# an opt-in feature. default-features=false + features=["wayland"] drops
|
||||||
mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
|
# clipboard_x11 + x11rb from the build. Branch yoda-x11-optional on
|
||||||
winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
|
# https://forge.aditua.com/leyoda/window_clipboard.
|
||||||
winit-core = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
|
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"
|
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]
|
[workspace.lints.rust]
|
||||||
rust_2018_idioms = { level = "deny", priority = -1 }
|
rust_2018_idioms = { level = "deny", priority = -1 }
|
||||||
|
|
@ -334,3 +367,6 @@ useless_conversion = "deny"
|
||||||
|
|
||||||
[workspace.lints.rustdoc]
|
[workspace.lints.rustdoc]
|
||||||
broken_intra_doc_links = "forbid"
|
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"]
|
tokio = ["accesskit_winit?/tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
accesskit = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14" }
|
accesskit = { git = "https://github.com/wash2/accesskit", rev = "6c20249" }
|
||||||
accesskit_windows = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true }
|
accesskit_windows = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true }
|
||||||
accesskit_macos = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true }
|
accesskit_macos = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true }
|
||||||
accesskit_winit = { git = "https://github.com/wash2/accesskit", tag = "cosmic-0.14", optional = true, default-features = false, features = [
|
accesskit_winit = { git = "https://github.com/wash2/accesskit", rev = "6c20249", optional = true, default-features = false, features = [
|
||||||
"rwh_06",
|
"rwh_06",
|
||||||
] }
|
] }
|
||||||
# accesskit = { path = "../../../accesskit/common" }
|
# 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 = []
|
basic-shaping = []
|
||||||
advanced-shaping = []
|
advanced-shaping = []
|
||||||
a11y = ["iced_accessibility"]
|
a11y = ["iced_accessibility"]
|
||||||
|
wayland = ["cctk"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
palette = "0.7"
|
palette = "0.7"
|
||||||
|
|
@ -31,6 +32,7 @@ num-traits.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
smol_str.workspace = true
|
smol_str.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
unicode-segmentation.workspace = true
|
||||||
web-time.workspace = true
|
web-time.workspace = true
|
||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
@ -42,8 +44,6 @@ window_clipboard.workspace = true
|
||||||
dnd.workspace = true
|
dnd.workspace = true
|
||||||
mime.workspace = true
|
mime.workspace = true
|
||||||
|
|
||||||
cctk.workspace = true
|
|
||||||
cctk.optional = true
|
|
||||||
# /TODO(POP)
|
# /TODO(POP)
|
||||||
[dependencies.iced_accessibility]
|
[dependencies.iced_accessibility]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -55,3 +55,10 @@ raw-window-handle.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
approx = "0.5"
|
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::id::Id;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ use crate::keyboard;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
/// A platform specific event for wayland
|
/// A platform specific event for wayland
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
/// A user interface event.
|
/// A user interface event.
|
||||||
|
|
@ -49,7 +50,7 @@ pub enum Event {
|
||||||
/// A platform specific event
|
/// A platform specific event
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum PlatformSpecific {
|
pub enum PlatformSpecific {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
/// A Wayland specific event
|
/// A Wayland specific event
|
||||||
Wayland(wayland::Event),
|
Wayland(wayland::Event),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,6 @@ pub enum Event {
|
||||||
Subsurface(SubsurfaceEvent),
|
Subsurface(SubsurfaceEvent),
|
||||||
/// Keyboard inhibit shortcuts
|
/// Keyboard inhibit shortcuts
|
||||||
ShortcutsInhibited(bool),
|
ShortcutsInhibited(bool),
|
||||||
|
/// Blur Support Enabled
|
||||||
|
BlurEnabled,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
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`]
|
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||||
/// children.
|
/// children.
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ pub enum Action {
|
||||||
SelectLine,
|
SelectLine,
|
||||||
/// Select the entire buffer.
|
/// Select the entire buffer.
|
||||||
SelectAll,
|
SelectAll,
|
||||||
|
/// Clear the selection
|
||||||
|
ClearSelection,
|
||||||
/// Perform an [`Edit`].
|
/// Perform an [`Edit`].
|
||||||
Edit(Edit),
|
Edit(Edit),
|
||||||
/// Click the [`Editor`] at the given [`Point`].
|
/// Click the [`Editor`] at the given [`Point`].
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub use scrollable::Scrollable;
|
||||||
pub use text_input::TextInput;
|
pub use text_input::TextInput;
|
||||||
|
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
use crate::{Rectangle, Vector};
|
use crate::{Rectangle, Vector, window};
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -75,6 +75,12 @@ pub trait Operation<T = ()>: Send {
|
||||||
fn finish(&self) -> Outcome<T> {
|
fn finish(&self) -> Outcome<T> {
|
||||||
Outcome::None
|
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>
|
impl<T, O> Operation<O> for Box<T>
|
||||||
|
|
@ -140,6 +146,14 @@ where
|
||||||
fn finish(&self) -> Outcome<O> {
|
fn finish(&self) -> Outcome<O> {
|
||||||
self.as_ref().finish()
|
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`].
|
/// The result of an [`Operation`].
|
||||||
|
|
@ -243,6 +257,14 @@ where
|
||||||
fn finish(&self) -> Outcome<O> {
|
fn finish(&self) -> Outcome<O> {
|
||||||
Outcome::None
|
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 }
|
BlackBox { operation }
|
||||||
|
|
@ -341,6 +363,10 @@ where
|
||||||
) {
|
) {
|
||||||
self.operation.custom(id, bounds, state);
|
self.operation.custom(id, bounds, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pre_operation(&mut self, id: Option<&Id>) {
|
||||||
|
self.operation.pre_operation(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.operation.traverse(&mut |operation| {
|
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 {
|
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 {
|
Chain {
|
||||||
|
|
@ -566,6 +608,14 @@ pub fn scoped<T: 'static>(
|
||||||
outcome => outcome,
|
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 {
|
ScopedOperation {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use super::Operation;
|
||||||
use crate::{
|
use crate::{
|
||||||
Rectangle,
|
Rectangle,
|
||||||
id::Id,
|
id::Id,
|
||||||
widget::operation::{Outcome, focusable::Count},
|
widget::operation::Outcome,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Produces an [`Operation`] that searches for the Id
|
/// Produces an [`Operation`] that searches for the Id
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,18 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse::{self, click};
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
|
use crate::text;
|
||||||
use crate::text::paragraph::{self, Paragraph};
|
use crate::text::paragraph::{self, Paragraph};
|
||||||
use crate::text::{self, Fragment};
|
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::tree::{self, Tree};
|
||||||
use crate::{
|
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};
|
pub use text::{Alignment, Ellipsize, LineHeight, Shaping, Wrapping};
|
||||||
|
|
||||||
/// A bunch of text.
|
/// A bunch of text.
|
||||||
|
|
@ -65,6 +67,7 @@ where
|
||||||
fragment: text::Fragment<'a>,
|
fragment: text::Fragment<'a>,
|
||||||
format: Format<Renderer::Font>,
|
format: Format<Renderer::Font>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
selectable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
|
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
|
||||||
|
|
@ -79,6 +82,7 @@ where
|
||||||
fragment: fragment.into_fragment(),
|
fragment: fragment.into_fragment(),
|
||||||
format: Format::default(),
|
format: Format::default(),
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
selectable: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +193,17 @@ where
|
||||||
{
|
{
|
||||||
let color = color.map(Into::into);
|
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`].
|
/// Sets the style class of the [`Text`].
|
||||||
|
|
@ -202,7 +216,116 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The internal state of a [`Text`] widget.
|
/// 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>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
for Text<'_, Theme, Renderer>
|
for Text<'_, Theme, Renderer>
|
||||||
|
|
@ -215,7 +338,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
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> {
|
fn size(&self) -> Size<Length> {
|
||||||
|
|
@ -231,8 +354,10 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
|
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||||
|
|
||||||
layout(
|
layout(
|
||||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
&mut state.paragraph,
|
||||||
renderer,
|
renderer,
|
||||||
limits,
|
limits,
|
||||||
&self.fragment,
|
&self.fragment,
|
||||||
|
|
@ -252,25 +377,372 @@ where
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||||
let style = theme.style(&self.class);
|
let style = theme.style(&self.class);
|
||||||
|
let bounds = layout.bounds();
|
||||||
draw(
|
let paragraph = state.paragraph.raw();
|
||||||
renderer,
|
let anchor = bounds.anchor(
|
||||||
defaults,
|
paragraph.min_bounds(),
|
||||||
layout.bounds(),
|
paragraph.align_x(),
|
||||||
state.raw(),
|
paragraph.align_y(),
|
||||||
style,
|
|
||||||
viewport,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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(
|
fn operate(
|
||||||
&mut self,
|
&mut self,
|
||||||
_tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
operation: &mut dyn super::Operation,
|
operation: &mut dyn super::Operation,
|
||||||
) {
|
) {
|
||||||
operation.text(None, layout.bounds(), &self.fragment);
|
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")]
|
#[cfg(feature = "a11y")]
|
||||||
|
|
@ -471,12 +943,28 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance of some text.
|
/// The appearance of some text.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
/// The [`Color`] of the text.
|
/// The [`Color`] of the text.
|
||||||
///
|
///
|
||||||
/// The default, `None`, means using the inherited color.
|
/// The default, `None`, means using the inherited color.
|
||||||
pub color: Option<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`].
|
/// The theme catalog of a [`Text`].
|
||||||
|
|
@ -510,13 +998,14 @@ impl Catalog for Theme {
|
||||||
|
|
||||||
/// The default text styling; color is inherited.
|
/// The default text styling; color is inherited.
|
||||||
pub fn default(_theme: &Theme) -> Style {
|
pub fn default(_theme: &Theme) -> Style {
|
||||||
Style { color: None }
|
Style::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text with the default base color.
|
/// Text with the default base color.
|
||||||
pub fn base(theme: &Theme) -> Style {
|
pub fn base(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.palette().text),
|
color: Some(theme.palette().text),
|
||||||
|
..Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,6 +1013,7 @@ pub fn base(theme: &Theme) -> Style {
|
||||||
pub fn primary(theme: &Theme) -> Style {
|
pub fn primary(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.palette().primary),
|
color: Some(theme.palette().primary),
|
||||||
|
..Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,6 +1021,7 @@ pub fn primary(theme: &Theme) -> Style {
|
||||||
pub fn secondary(theme: &Theme) -> Style {
|
pub fn secondary(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.extended_palette().secondary.base.color),
|
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 {
|
pub fn success(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.palette().success),
|
color: Some(theme.palette().success),
|
||||||
|
..Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -545,6 +1037,7 @@ pub fn success(theme: &Theme) -> Style {
|
||||||
pub fn warning(theme: &Theme) -> Style {
|
pub fn warning(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.palette().warning),
|
color: Some(theme.palette().warning),
|
||||||
|
..Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -552,5 +1045,206 @@ pub fn warning(theme: &Theme) -> Style {
|
||||||
pub fn danger(theme: &Theme) -> Style {
|
pub fn danger(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.palette().danger),
|
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::Center;
|
||||||
use iced::widget::{Column, button, column, text};
|
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run(Counter::update, Counter::view)
|
iced::run(Counter::update, Counter::view)
|
||||||
|
|
@ -42,7 +42,7 @@ impl Counter {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use iced_test::{Error, simulator};
|
use iced_test::{simulator, Error};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_counts() -> Result<(), Error> {
|
fn it_counts() -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ impl Pipeline {
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
mag_filter: wgpu::FilterMode::Linear,
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
min_filter: wgpu::FilterMode::Linear,
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
mipmap_filter: wgpu::FilterMode::Linear,
|
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -223,7 +223,7 @@ impl Pipeline {
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("cubes pipeline layout"),
|
label: Some("cubes pipeline layout"),
|
||||||
bind_group_layouts: &[&uniform_bind_group_layout],
|
bind_group_layouts: &[&uniform_bind_group_layout],
|
||||||
push_constant_ranges: &[],
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader =
|
let shader =
|
||||||
|
|
@ -280,7 +280,7 @@ impl Pipeline {
|
||||||
compilation_options:
|
compilation_options:
|
||||||
wgpu::PipelineCompilationOptions::default(),
|
wgpu::PipelineCompilationOptions::default(),
|
||||||
}),
|
}),
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -388,6 +388,7 @@ impl Pipeline {
|
||||||
),
|
),
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
pass.set_viewport(
|
pass.set_viewport(
|
||||||
|
|
@ -478,7 +479,7 @@ impl DepthPipeline {
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("cubes.depth_pipeline.layout"),
|
label: Some("cubes.depth_pipeline.layout"),
|
||||||
bind_group_layouts: &[&bind_group_layout],
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
push_constant_ranges: &[],
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader =
|
let shader =
|
||||||
|
|
@ -520,7 +521,7 @@ impl DepthPipeline {
|
||||||
compilation_options:
|
compilation_options:
|
||||||
wgpu::PipelineCompilationOptions::default(),
|
wgpu::PipelineCompilationOptions::default(),
|
||||||
}),
|
}),
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -586,6 +587,7 @@ impl DepthPipeline {
|
||||||
),
|
),
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
pass.set_viewport(
|
pass.set_viewport(
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ impl Scene {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,8 +65,8 @@ fn build_pipeline(
|
||||||
let pipeline_layout =
|
let pipeline_layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[],
|
bind_group_layouts: &[],
|
||||||
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
|
@ -101,7 +102,7 @@ fn build_pipeline(
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,20 @@ use std::{
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced::{Element, id::Id};
|
|
||||||
use iced::{
|
use iced::{
|
||||||
Event, Length, Rectangle,
|
|
||||||
clipboard::{
|
clipboard::{
|
||||||
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
||||||
mime::AllowedMimeTypes,
|
mime::AllowedMimeTypes,
|
||||||
},
|
},
|
||||||
event,
|
event,
|
||||||
id::Internal,
|
id::Internal,
|
||||||
mouse, overlay,
|
mouse, overlay, Event, Length, Rectangle,
|
||||||
};
|
};
|
||||||
|
use iced::{id::Id, Element};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
self, Clipboard, Layout, Shell, Widget, layout,
|
self, layout,
|
||||||
widget::{Tree, tree},
|
widget::{tree, Tree},
|
||||||
|
Clipboard, Layout, Shell, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn dnd_destination<'a, Message: 'static>(
|
pub fn dnd_destination<'a, Message: 'static>(
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use iced::Element;
|
|
||||||
use iced::id::Id;
|
use iced::id::Id;
|
||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
|
use iced::Element;
|
||||||
use iced::{
|
use iced::{
|
||||||
Event, Length, Point, Rectangle,
|
|
||||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
||||||
event, mouse, overlay,
|
event, mouse, overlay, Event, Length, Point, Rectangle,
|
||||||
};
|
};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, Shell, layout, renderer,
|
layout, renderer,
|
||||||
widget::{Tree, tree},
|
widget::{tree, Tree},
|
||||||
|
Clipboard, Shell,
|
||||||
};
|
};
|
||||||
use iced_core::{Layout, Widget};
|
use iced_core::{Layout, Widget};
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ impl<
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
AppMessage: 'static,
|
AppMessage: 'static,
|
||||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||||
> DndSource<'a, Message, AppMessage, D>
|
> DndSource<'a, Message, AppMessage, D>
|
||||||
{
|
{
|
||||||
pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
|
pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -123,7 +123,7 @@ impl<
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
AppMessage: 'static,
|
AppMessage: 'static,
|
||||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||||
> Widget<Message, iced::Theme, iced::Renderer>
|
> Widget<Message, iced::Theme, iced::Renderer>
|
||||||
for DndSource<'a, Message, AppMessage, D>
|
for DndSource<'a, Message, AppMessage, D>
|
||||||
{
|
{
|
||||||
fn children(&self) -> Vec<Tree> {
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
|
@ -361,7 +361,7 @@ impl<
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
AppMessage: 'static,
|
AppMessage: 'static,
|
||||||
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
|
||||||
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
|
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
|
||||||
{
|
{
|
||||||
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
|
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
|
||||||
Element::new(e)
|
Element::new(e)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use cctk::sctk::reexports::{
|
||||||
|
|
||||||
use iced::platform_specific::shell::commands::subsurface::get_subsurface;
|
use iced::platform_specific::shell::commands::subsurface::get_subsurface;
|
||||||
use iced::{
|
use iced::{
|
||||||
Element, Length, Subscription, Task,
|
|
||||||
event::wayland::Event as WaylandEvent,
|
event::wayland::Event as WaylandEvent,
|
||||||
platform_specific::{
|
platform_specific::{
|
||||||
runtime::wayland::subsurface::SctkSubsurfaceSettings,
|
runtime::wayland::subsurface::SctkSubsurfaceSettings,
|
||||||
|
|
@ -15,6 +14,7 @@ use iced::{
|
||||||
},
|
},
|
||||||
widget::{button, column, text, text_input},
|
widget::{button, column, text, text_input},
|
||||||
window::{self, Id, Settings},
|
window::{self, Id, Settings},
|
||||||
|
Element, Length, Subscription, Task,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,7 @@ fn delete_icon() -> Text<'static> {
|
||||||
fn subtle(theme: &Theme) -> text::Style {
|
fn subtle(theme: &Theme) -> text::Style {
|
||||||
text::Style {
|
text::Style {
|
||||||
color: Some(theme.extended_palette().background.strongest.color),
|
color: Some(theme.extended_palette().background.strongest.color),
|
||||||
|
..text::Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! Load and operate on images.
|
//! Load and operate on images.
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
use crate::core::Bytes;
|
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::Rectangle;
|
||||||
use crate::core::image;
|
use crate::core::image;
|
||||||
use crate::core::svg;
|
use crate::core::svg;
|
||||||
|
|
@ -180,3 +180,105 @@ fn to_error(error: ::image::ImageError) -> image::Error {
|
||||||
error => image::Error::Invalid(Arc::new(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
|
/// Pushes a new clipping region in the [`Stack`]; creating a new layer in the
|
||||||
/// process.
|
/// process.
|
||||||
pub fn push_clip(&mut self, bounds: Rectangle) {
|
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.previous.push(self.current);
|
||||||
|
|
||||||
self.current = self.active_count;
|
self.current = self.active_count;
|
||||||
self.active_count += 1;
|
self.active_count += 1;
|
||||||
|
|
||||||
let bounds = bounds * self.transformation();
|
|
||||||
|
|
||||||
if self.current == self.layers.len() {
|
if self.current == self.layers.len() {
|
||||||
self.layers.push(T::with_bounds(bounds));
|
self.layers.push(T::with_bounds(bounds));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,14 @@ impl FontSystem {
|
||||||
&mut self.raw
|
&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.
|
/// Loads a font from its bytes.
|
||||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||||
if let Cow::Borrowed(bytes) = bytes {
|
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
|
// Editing events
|
||||||
Action::Edit(edit) => {
|
Action::Edit(edit) => {
|
||||||
let topmost_line_before_edit = editor
|
let topmost_line_before_edit = editor
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ workspace = true
|
||||||
debug = []
|
debug = []
|
||||||
selector = ["dep:iced_selector"]
|
selector = ["dep:iced_selector"]
|
||||||
a11y = ["iced_accessibility", "iced_core/a11y"]
|
a11y = ["iced_accessibility", "iced_core/a11y"]
|
||||||
cctk = ["iced_core/cctk", "dep:cctk"]
|
wayland = ["iced_core/wayland", "dep:cctk"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
|
|
@ -33,9 +33,14 @@ sipper.optional = true
|
||||||
iced_selector.workspace = true
|
iced_selector.workspace = true
|
||||||
iced_selector.optional = true
|
iced_selector.optional = true
|
||||||
iced_futures.features = ["thread-pool"]
|
iced_futures.features = ["thread-pool"]
|
||||||
cctk.workspace = true
|
|
||||||
cctk.optional = true
|
|
||||||
iced_accessibility.workspace = true
|
iced_accessibility.workspace = true
|
||||||
iced_accessibility.optional = true
|
iced_accessibility.optional = true
|
||||||
window_clipboard.workspace = true
|
window_clipboard.workspace = true
|
||||||
dnd.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.
|
//! Access the clipboard.
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
use dnd::{DndDestinationRectangle, DndSurface};
|
use dnd::{DndDestinationRectangle, DndSurface};
|
||||||
use iced_core::clipboard::DndSource;
|
use window_clipboard::mime::AllowedMimeTypes;
|
||||||
use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
|
|
||||||
|
|
||||||
use crate::{oneshot, task, Action, Task};
|
use crate::{Action, Task, oneshot, task};
|
||||||
|
|
||||||
/// An action to be performed on the system.
|
/// An action to be performed on the system.
|
||||||
pub enum DndAction {
|
pub enum DndAction {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ pub use task::Task;
|
||||||
pub use user_interface::UserInterface;
|
pub use user_interface::UserInterface;
|
||||||
pub use window::Window;
|
pub use window::Window;
|
||||||
|
|
||||||
use crate::core::Color;
|
|
||||||
use crate::futures::futures::channel::oneshot;
|
use crate::futures::futures::channel::oneshot;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,23 @@
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
/// Platform specific actions defined for wayland
|
/// Platform specific actions defined for wayland
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
/// Platform specific actions defined for wayland
|
/// Platform specific actions defined for wayland
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Wayland Specific Actions
|
/// Wayland Specific Actions
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
Wayland(wayland::Action),
|
Wayland(wayland::Action),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Action {
|
impl fmt::Debug for Action {
|
||||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
Action::Wayland(action) => action.fmt(_f),
|
Action::Wayland(action) => action.fmt(_f),
|
||||||
#[cfg(not(all(feature = "cctk", target_os = "linux")))]
|
#[cfg(not(wayland_platform))]
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl fmt::Debug for Action {
|
||||||
f,
|
f,
|
||||||
"Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}",
|
"Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}",
|
||||||
window, token,
|
window, token,
|
||||||
)
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use cctk::sctk::{
|
||||||
};
|
};
|
||||||
use iced_core::layout::Limits;
|
use iced_core::layout::Limits;
|
||||||
|
|
||||||
use iced_core::window::Id;
|
|
||||||
use iced_core::Rectangle;
|
use iced_core::Rectangle;
|
||||||
|
use iced_core::window::Id;
|
||||||
|
|
||||||
/// output for layer surface
|
/// output for layer surface
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -150,6 +150,18 @@ pub enum Action {
|
||||||
/// layer of the layer surface
|
/// layer of the layer surface
|
||||||
layer: Layer,
|
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 {
|
impl fmt::Debug for Action {
|
||||||
|
|
@ -162,35 +174,56 @@ impl fmt::Debug for Action {
|
||||||
),
|
),
|
||||||
Action::Size { id, width, height } => write!(
|
Action::Size { id, width, height } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height
|
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}",
|
||||||
|
id, width, height
|
||||||
),
|
),
|
||||||
Action::Destroy(id) => write!(
|
Action::Destroy(id) => write!(
|
||||||
f,
|
f,
|
||||||
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id
|
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}",
|
||||||
|
id
|
||||||
),
|
),
|
||||||
Action::Anchor { id, anchor } => write!(
|
Action::Anchor { id, anchor } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor
|
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}",
|
||||||
|
id, anchor
|
||||||
),
|
),
|
||||||
Action::ExclusiveZone { id, exclusive_zone } => write!(
|
Action::ExclusiveZone { id, exclusive_zone } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id
|
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}",
|
||||||
|
id
|
||||||
),
|
),
|
||||||
Action::Margin { id, margin } => write!(
|
Action::Margin { id, margin } => write!(
|
||||||
f,
|
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,
|
f,
|
||||||
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity
|
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}",
|
||||||
|
id, keyboard_interactivity
|
||||||
),
|
),
|
||||||
Action::InputZone { id, zone } => write!(
|
Action::InputZone { id, zone } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}", id, zone
|
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}",
|
||||||
|
id, zone
|
||||||
),
|
),
|
||||||
Action::Layer { id, layer } => write!(
|
Action::Layer { id, layer } => write!(
|
||||||
f,
|
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
|
||||||
height: u32,
|
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 {
|
impl fmt::Debug for Action {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Action::Popup { popup, .. } => write!(
|
Action::Popup { popup, .. } => {
|
||||||
f,
|
write!(f, "Action::PopupAction::Popup {{ popup: {:?} }}", popup)
|
||||||
"Action::PopupAction::Popup {{ popup: {:?} }}",
|
}
|
||||||
popup
|
Action::Destroy { id } => {
|
||||||
),
|
write!(f, "Action::PopupAction::Destroy {{ id: {:?} }}", id)
|
||||||
Action::Destroy { id } => write!(
|
}
|
||||||
f,
|
|
||||||
"Action::PopupAction::Destroy {{ id: {:?} }}",
|
|
||||||
id
|
|
||||||
),
|
|
||||||
Action::Size { id, width, height } => write!(
|
Action::Size { id, width, height } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}",
|
"Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}",
|
||||||
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.
|
//! Find and query widgets in your applications.
|
||||||
pub use iced_selector::{
|
pub use iced_selector::{
|
||||||
Bounded, Candidate, Selector, Target, Text, id, is_focused,
|
Bounded, Candidate, Selector, Target, Text, focus, id, is_focused,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Task;
|
use crate::Task;
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ pub fn frames() -> Subscription<(Id, Instant)> {
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
/// Subscribes to the frames of the window of the running application.
|
/// Subscribes to the frames of the window of the running application.
|
||||||
///
|
///
|
||||||
/// The resulting [`Subscription`] will produce items at a rate equal to the
|
/// 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::Selector;
|
||||||
use crate::core::widget::operation::{
|
use crate::core::widget::operation::{
|
||||||
Focusable, Outcome, Scrollable, TextInput,
|
Focusable, Outcome, Scrollable, TextInput,
|
||||||
|
|
@ -100,6 +102,10 @@ where
|
||||||
fn finish(&self) -> Self::Output {
|
fn finish(&self) -> Self::Output {
|
||||||
self.outputs.clone()
|
self.outputs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_window_id(&mut self, id: window::Id) {
|
||||||
|
self.selector.set_window_id(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Strategy {
|
pub trait Strategy {
|
||||||
|
|
@ -110,6 +116,8 @@ pub trait Strategy {
|
||||||
fn is_done(&self) -> bool;
|
fn is_done(&self) -> bool;
|
||||||
|
|
||||||
fn finish(&self) -> Self::Output;
|
fn finish(&self) -> Self::Output;
|
||||||
|
|
||||||
|
fn set_window_id(&mut self, _id: window::Id) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -273,4 +281,12 @@ where
|
||||||
fn finish(&self) -> Outcome<S::Output> {
|
fn finish(&self) -> Outcome<S::Output> {
|
||||||
Outcome::Some(self.strategy.finish())
|
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.
|
//! Select data from the widget tree.
|
||||||
use iced_core as core;
|
use iced_core as core;
|
||||||
|
use iced_core::window;
|
||||||
|
|
||||||
mod find;
|
mod find;
|
||||||
mod target;
|
mod target;
|
||||||
|
|
@ -48,6 +49,8 @@ pub trait Selector {
|
||||||
{
|
{
|
||||||
FindAll::new(find::All::new(self))
|
FindAll::new(find::All::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_window_id(&mut self, _id: window::Id) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selector for &str {
|
impl Selector for &str {
|
||||||
|
|
@ -171,3 +174,42 @@ pub fn is_focused() -> impl Selector<Output = Target> {
|
||||||
|
|
||||||
IsFocused
|
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,
|
bounds: Rectangle,
|
||||||
visible_bounds: Option<Rectangle>,
|
visible_bounds: Option<Rectangle>,
|
||||||
},
|
},
|
||||||
|
PreOperation {
|
||||||
|
id: Option<Id>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Target {
|
impl Target {
|
||||||
|
|
@ -54,6 +57,7 @@ impl Target {
|
||||||
| Target::TextInput { bounds, .. }
|
| Target::TextInput { bounds, .. }
|
||||||
| Target::Text { bounds, .. }
|
| Target::Text { bounds, .. }
|
||||||
| Target::Custom { bounds, .. } => *bounds,
|
| Target::Custom { bounds, .. } => *bounds,
|
||||||
|
Target::PreOperation { .. } => Rectangle::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,6 +70,7 @@ impl Target {
|
||||||
| Target::TextInput { visible_bounds, .. }
|
| Target::TextInput { visible_bounds, .. }
|
||||||
| Target::Text { visible_bounds, .. }
|
| Target::Text { visible_bounds, .. }
|
||||||
| Target::Custom { visible_bounds, .. } => *visible_bounds,
|
| Target::Custom { visible_bounds, .. } => *visible_bounds,
|
||||||
|
Target::PreOperation { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +143,9 @@ impl From<Candidate<'_>> for Target {
|
||||||
bounds,
|
bounds,
|
||||||
visible_bounds,
|
visible_bounds,
|
||||||
},
|
},
|
||||||
|
Candidate::PreOperation { id } => {
|
||||||
|
Self::PreOperation { id: id.cloned() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +203,8 @@ pub enum Candidate<'a> {
|
||||||
visible_bounds: Option<Rectangle>,
|
visible_bounds: Option<Rectangle>,
|
||||||
state: &'a dyn Any,
|
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> {
|
impl<'a> Candidate<'a> {
|
||||||
|
|
@ -206,7 +216,8 @@ impl<'a> Candidate<'a> {
|
||||||
| Candidate::Scrollable { id, .. }
|
| Candidate::Scrollable { id, .. }
|
||||||
| Candidate::TextInput { id, .. }
|
| Candidate::TextInput { id, .. }
|
||||||
| Candidate::Text { 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::TextInput { bounds, .. }
|
||||||
| Candidate::Text { bounds, .. }
|
| Candidate::Text { bounds, .. }
|
||||||
| Candidate::Custom { bounds, .. } => *bounds,
|
| Candidate::Custom { bounds, .. } => *bounds,
|
||||||
|
Candidate::PreOperation { .. } => Rectangle::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,6 +243,7 @@ impl<'a> Candidate<'a> {
|
||||||
| Candidate::TextInput { visible_bounds, .. }
|
| Candidate::TextInput { visible_bounds, .. }
|
||||||
| Candidate::Text { visible_bounds, .. }
|
| Candidate::Text { visible_bounds, .. }
|
||||||
| Candidate::Custom { visible_bounds, .. } => *visible_bounds,
|
| Candidate::Custom { visible_bounds, .. } => *visible_bounds,
|
||||||
|
Candidate::PreOperation { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -606,7 +606,7 @@ pub mod font {
|
||||||
pub mod event {
|
pub mod event {
|
||||||
//! Handle events of a user interface.
|
//! Handle events of a user interface.
|
||||||
pub use crate::core::event::PlatformSpecific;
|
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::wayland;
|
||||||
pub use crate::core::event::{Event, Status};
|
pub use crate::core::event::{Event, Status};
|
||||||
pub use iced_futures::event::{listen, listen_raw, listen_with};
|
pub use iced_futures::event::{listen, listen_raw, listen_with};
|
||||||
|
|
@ -683,6 +683,8 @@ pub use event::Event;
|
||||||
pub use executor::Executor;
|
pub use executor::Executor;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
|
#[cfg(feature = "canvas")]
|
||||||
|
pub use renderer::graphics::geometry;
|
||||||
pub use task::Task;
|
pub use task::Task;
|
||||||
pub use window::Window;
|
pub use window::Window;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,11 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
runtime::Action::Reload => {
|
runtime::Action::Reload => {
|
||||||
// TODO
|
// 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,
|
&mut self.renderer,
|
||||||
theme,
|
theme,
|
||||||
&renderer::Style {
|
&renderer::Style {
|
||||||
|
icon_color: style.text_color,
|
||||||
text_color: style.text_color,
|
text_color: style.text_color,
|
||||||
|
scale_factor: f64::from(scale_factor),
|
||||||
},
|
},
|
||||||
mouse::Cursor::Unavailable,
|
mouse::Cursor::Unavailable,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -209,17 +209,19 @@ where
|
||||||
&mut self.messages,
|
&mut self.messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let scale_factor = 2.0_f32;
|
||||||
|
|
||||||
self.raw.draw(
|
self.raw.draw(
|
||||||
&mut self.renderer,
|
&mut self.renderer,
|
||||||
theme,
|
theme,
|
||||||
&core::renderer::Style {
|
&core::renderer::Style {
|
||||||
|
icon_color: base.text_color,
|
||||||
text_color: base.text_color,
|
text_color: base.text_color,
|
||||||
|
scale_factor: f64::from(scale_factor),
|
||||||
},
|
},
|
||||||
self.cursor,
|
self.cursor,
|
||||||
);
|
);
|
||||||
|
|
||||||
let scale_factor = 2.0;
|
|
||||||
|
|
||||||
let physical_size = Size::new(
|
let physical_size = Size::new(
|
||||||
(self.size.width * scale_factor).round() as u32,
|
(self.size.width * scale_factor).round() as u32,
|
||||||
(self.size.height * scale_factor).round() as u32,
|
(self.size.height * scale_factor).round() as u32,
|
||||||
|
|
|
||||||
|
|
@ -775,6 +775,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
..text::Style::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
},
|
},
|
||||||
|
|
@ -945,6 +946,7 @@ where
|
||||||
text(label).size(14).style(|theme: &core::Theme| {
|
text(label).size(14).style(|theme: &core::Theme| {
|
||||||
text::Style {
|
text::Style {
|
||||||
color: Some(theme.extended_palette().background.weak.text),
|
color: Some(theme.extended_palette().background.weak.text),
|
||||||
|
..text::Style::default()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
space::horizontal(),
|
space::horizontal(),
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,10 @@ impl Engine {
|
||||||
} else {
|
} else {
|
||||||
// Draw corners that have too small border radii as having no border radius,
|
// 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.
|
// 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(
|
let mut temp_pixmap = tiny_skia::Pixmap::new(
|
||||||
path_bounds.width as u32,
|
path_bounds.width as u32,
|
||||||
path_bounds.height as u32,
|
path_bounds.height as u32,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,21 @@ impl Pipeline {
|
||||||
) {
|
) {
|
||||||
let mut cache = self.cache.borrow_mut();
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -110,6 +124,8 @@ impl Pipeline {
|
||||||
struct Cache {
|
struct Cache {
|
||||||
entries: FxHashMap<raster::Id, Option<Entry>>,
|
entries: FxHashMap<raster::Id, Option<Entry>>,
|
||||||
hits: FxHashSet<raster::Id>,
|
hits: FxHashSet<raster::Id>,
|
||||||
|
resampled: FxHashMap<(raster::Id, u32, u32), Entry>,
|
||||||
|
resampled_hits: FxHashSet<(raster::Id, u32, u32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
|
|
@ -166,9 +182,59 @@ impl Cache {
|
||||||
Ok(ret)
|
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) {
|
fn trim(&mut self) {
|
||||||
self.entries.retain(|key, _| self.hits.contains(key));
|
self.entries.retain(|key, _| self.hits.contains(key));
|
||||||
|
self.resampled
|
||||||
|
.retain(|key, _| self.resampled_hits.contains(key));
|
||||||
self.hits.clear();
|
self.hits.clear();
|
||||||
|
self.resampled_hits.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::core::svg::{Data, Handle};
|
use crate::core::svg::{Data, Handle};
|
||||||
use crate::core::{Color, Rectangle, Size};
|
use crate::core::{Color, Rectangle, Size};
|
||||||
|
use crate::graphics::text;
|
||||||
|
|
||||||
use resvg::usvg;
|
use resvg::usvg;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
@ -74,6 +75,7 @@ struct Cache {
|
||||||
rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
|
rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
|
||||||
raster_hits: FxHashSet<RasterKey>,
|
raster_hits: FxHashSet<RasterKey>,
|
||||||
fontdb: Option<Arc<usvg::fontdb::Database>>,
|
fontdb: Option<Arc<usvg::fontdb::Database>>,
|
||||||
|
fontdb_version: Option<text::Version>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -87,12 +89,15 @@ impl Cache {
|
||||||
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
||||||
let id = handle.id();
|
let id = handle.id();
|
||||||
|
|
||||||
// TODO: Reuse `cosmic-text` font database
|
// Reuse the font database already loaded by the `cosmic-text` font
|
||||||
if self.fontdb.is_none() {
|
// system. We clone it once and refresh only when a font is
|
||||||
let mut fontdb = usvg::fontdb::Database::new();
|
// loaded (i.e. when the font system's version changes).
|
||||||
fontdb.load_system_fonts();
|
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 {
|
let options = usvg::Options {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ svg = ["iced_graphics/svg", "resvg/text"]
|
||||||
web-colors = ["iced_graphics/web-colors"]
|
web-colors = ["iced_graphics/web-colors"]
|
||||||
webgl = ["wgpu/webgl"]
|
webgl = ["wgpu/webgl"]
|
||||||
strict-assertions = []
|
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]
|
[dependencies]
|
||||||
iced_debug.workspace = true
|
iced_debug.workspace = true
|
||||||
|
|
@ -57,11 +63,14 @@ wayland-protocols.workspace = true
|
||||||
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||||
wayland-client = { version = "0.31.2" }
|
wayland-client = { version = "0.31.2" }
|
||||||
wayland-sys = { version = "0.31.1", features = ["dlopen"] }
|
wayland-sys = { version = "0.31.1", features = ["dlopen"] }
|
||||||
as-raw-xcb-connection = "1.0.1"
|
as-raw-xcb-connection = { version = "1.0.1", optional = true }
|
||||||
tiny-xlib = "0.2.3"
|
tiny-xlib = { version = "0.2.3", optional = true }
|
||||||
x11rb = { version = "0.13.1", features = [
|
x11rb = { version = "0.13.1", optional = true, features = [
|
||||||
"allow-unsafe-code",
|
"allow-unsafe-code",
|
||||||
"dl-libxcb",
|
"dl-libxcb",
|
||||||
"dri3",
|
"dri3",
|
||||||
"randr",
|
"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.
|
/// Returns the size of the written bytes.
|
||||||
pub fn write(
|
pub fn write(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
|
|
@ -79,7 +78,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
||||||
&self.raw,
|
&self.raw,
|
||||||
(offset + bytes_written) as u64,
|
(offset + bytes_written) as u64,
|
||||||
MAX_WRITE_SIZE_U64,
|
MAX_WRITE_SIZE_U64,
|
||||||
device,
|
|
||||||
)
|
)
|
||||||
.copy_from_slice(
|
.copy_from_slice(
|
||||||
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
|
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
|
||||||
|
|
@ -100,7 +98,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
||||||
&self.raw,
|
&self.raw,
|
||||||
(offset + bytes_written) as u64,
|
(offset + bytes_written) as u64,
|
||||||
bytes_left,
|
bytes_left,
|
||||||
device,
|
|
||||||
)
|
)
|
||||||
.copy_from_slice(&bytes[bytes_written..]);
|
.copy_from_slice(&bytes[bytes_written..]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ pub fn convert(
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
|
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
|
||||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||||
push_constant_ranges: &[],
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
|
@ -149,7 +149,7 @@ pub fn convert(
|
||||||
},
|
},
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: wgpu::MultisampleState::default(),
|
multisample: wgpu::MultisampleState::default(),
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -194,6 +194,7 @@ pub fn convert(
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
pass.set_pipeline(&pipeline);
|
pass.set_pipeline(&pipeline);
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ impl Atlas {
|
||||||
match &entry {
|
match &entry {
|
||||||
Entry::Contiguous(allocation) => {
|
Entry::Contiguous(allocation) => {
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
pixels, width, 0, allocation, device, encoder, belt,
|
pixels, width, 0, allocation, encoder, belt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Entry::Fragmented { fragments, .. } => {
|
Entry::Fragmented { fragments, .. } => {
|
||||||
|
|
@ -145,7 +145,6 @@ impl Atlas {
|
||||||
width,
|
width,
|
||||||
offset,
|
offset,
|
||||||
&fragment.allocation,
|
&fragment.allocation,
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
belt,
|
belt,
|
||||||
);
|
);
|
||||||
|
|
@ -312,7 +311,6 @@ impl Atlas {
|
||||||
image_width: u32,
|
image_width: u32,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
allocation: &Allocation,
|
allocation: &Allocation,
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
) {
|
) {
|
||||||
|
|
@ -334,7 +332,6 @@ impl Atlas {
|
||||||
let buffer_slice = belt.allocate(
|
let buffer_slice = belt.allocate(
|
||||||
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
|
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
|
||||||
wgpu::BufferSize::new(8 * 4).unwrap(),
|
wgpu::BufferSize::new(8 * 4).unwrap(),
|
||||||
device,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const PIXEL: usize = 4;
|
const PIXEL: usize = 4;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ use crate::image::atlas::{self, Atlas};
|
||||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||||
use worker::Worker;
|
use worker::Worker;
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|
@ -38,7 +40,13 @@ impl Cache {
|
||||||
raster: Raster {
|
raster: Raster {
|
||||||
cache: crate::image::raster::Cache::default(),
|
cache: crate::image::raster::Cache::default(),
|
||||||
pending: HashMap::new(),
|
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")]
|
#[cfg(feature = "svg")]
|
||||||
vector: crate::image::vector::Cache::default(),
|
vector: crate::image::vector::Cache::default(),
|
||||||
|
|
@ -206,7 +214,9 @@ impl Cache {
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
handle: &core::image::Handle,
|
handle: &core::image::Handle,
|
||||||
|
bounds: Size<f32>,
|
||||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||||
|
use crate::graphics::image::{downsample, downsample_target, load};
|
||||||
use crate::image::raster::Memory;
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
self.receive();
|
self.receive();
|
||||||
|
|
@ -220,6 +230,34 @@ impl Cache {
|
||||||
None,
|
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 {
|
if let Memory::Device {
|
||||||
entry, bind_group, ..
|
entry, bind_group, ..
|
||||||
} = memory
|
} = memory
|
||||||
|
|
@ -300,6 +338,23 @@ impl Cache {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
self.worker.drop(_bind_group);
|
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")]
|
#[cfg(feature = "svg")]
|
||||||
|
|
@ -373,6 +428,9 @@ impl Drop for Cache {
|
||||||
struct Raster {
|
struct Raster {
|
||||||
cache: crate::image::raster::Cache,
|
cache: crate::image::raster::Cache,
|
||||||
pending: HashMap<core::image::Id, Vec<Callback>>,
|
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,
|
belt: wgpu::util::StagingBelt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,7 +509,10 @@ mod worker {
|
||||||
backend,
|
backend,
|
||||||
texture_layout,
|
texture_layout,
|
||||||
shell: shell.clone(),
|
shell: shell.clone(),
|
||||||
belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
|
belt: wgpu::util::StagingBelt::new(
|
||||||
|
device.clone(),
|
||||||
|
4 * 1024 * 1024,
|
||||||
|
),
|
||||||
jobs: jobs_receiver,
|
jobs: jobs_receiver,
|
||||||
output: work_sender,
|
output: work_sender,
|
||||||
quit: quit_receiver,
|
quit: quit_receiver,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ impl Pipeline {
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
min_filter: wgpu::FilterMode::Nearest,
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
mag_filter: wgpu::FilterMode::Nearest,
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ impl Pipeline {
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
min_filter: wgpu::FilterMode::Linear,
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
mag_filter: wgpu::FilterMode::Linear,
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
mipmap_filter: wgpu::FilterMode::Linear,
|
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -106,8 +106,8 @@ impl Pipeline {
|
||||||
let layout =
|
let layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::image pipeline layout"),
|
label: Some("iced_wgpu::image pipeline layout"),
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||||
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader =
|
let shader =
|
||||||
|
|
@ -191,7 +191,7 @@ impl Pipeline {
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -267,7 +267,13 @@ impl State {
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
} => {
|
} => {
|
||||||
if let Some((atlas_entry, bind_group)) = cache
|
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() {
|
match atlas.as_mut() {
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -528,7 +534,6 @@ impl Layer {
|
||||||
&self.uniforms,
|
&self.uniforms,
|
||||||
0,
|
0,
|
||||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||||
device,
|
|
||||||
)
|
)
|
||||||
.copy_from_slice(bytes);
|
.copy_from_slice(bytes);
|
||||||
|
|
||||||
|
|
@ -539,11 +544,11 @@ impl Layer {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
if !nearest.is_empty() {
|
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() {
|
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
|
// It would be great if the `StagingBelt` API exposed methods
|
||||||
// for introspection to detect when a resize may be worth it.
|
// for introspection to detect when a resize may be worth it.
|
||||||
staging_belt: wgpu::util::StagingBelt::new(
|
staging_belt: wgpu::util::StagingBelt::new(
|
||||||
|
engine.device.clone(),
|
||||||
buffer::MAX_WRITE_SIZE as u64,
|
buffer::MAX_WRITE_SIZE as u64,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -454,6 +455,7 @@ impl Renderer {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -528,6 +530,7 @@ impl Renderer {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -621,6 +624,7 @@ impl Renderer {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ impl Layer {
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) {
|
) {
|
||||||
self.update(device, encoder, belt, transformation, scale);
|
self.update(encoder, belt, transformation, scale);
|
||||||
|
|
||||||
if !quads.solids.is_empty() {
|
if !quads.solids.is_empty() {
|
||||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||||
|
|
@ -223,7 +223,6 @@ impl Layer {
|
||||||
|
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
|
|
@ -237,7 +236,6 @@ impl Layer {
|
||||||
&self.constants_buffer,
|
&self.constants_buffer,
|
||||||
0,
|
0,
|
||||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||||
device,
|
|
||||||
)
|
)
|
||||||
.copy_from_slice(bytes);
|
.copy_from_slice(bytes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl Layer {
|
||||||
instances: &[Gradient],
|
instances: &[Gradient],
|
||||||
) {
|
) {
|
||||||
let _ = self.instances.resize(device, instances.len());
|
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();
|
self.instance_count = instances.len();
|
||||||
}
|
}
|
||||||
|
|
@ -75,8 +75,8 @@ impl Pipeline {
|
||||||
let layout = device.create_pipeline_layout(
|
let layout = device.create_pipeline_layout(
|
||||||
&wgpu::PipelineLayoutDescriptor {
|
&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu.quad.gradient.pipeline"),
|
label: Some("iced_wgpu.quad.gradient.pipeline"),
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[constants_layout],
|
bind_group_layouts: &[constants_layout],
|
||||||
|
immediate_size: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ impl Pipeline {
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ impl Layer {
|
||||||
instances: &[Solid],
|
instances: &[Solid],
|
||||||
) {
|
) {
|
||||||
let _ = self.instances.resize(device, instances.len());
|
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();
|
self.instance_count = instances.len();
|
||||||
}
|
}
|
||||||
|
|
@ -65,8 +65,8 @@ impl Pipeline {
|
||||||
let layout =
|
let layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu.quad.solid.pipeline"),
|
label: Some("iced_wgpu.quad.solid.pipeline"),
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[constants_layout],
|
bind_group_layouts: &[constants_layout],
|
||||||
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader =
|
let shader =
|
||||||
|
|
@ -139,7 +139,7 @@ impl Pipeline {
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -619,7 +619,7 @@ fn prepare(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Some(cryoglyph::TextArea {
|
Some(cryoglyph::TextArea {
|
||||||
buffer,
|
text: buffer.layout_runs(),
|
||||||
left: position.x,
|
left: position.x,
|
||||||
top: position.y,
|
top: position.y,
|
||||||
scale: transformation.scale_factor()
|
scale: transformation.scale_factor()
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ fn render<'a>(
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -425,18 +426,13 @@ impl Layer {
|
||||||
|
|
||||||
let indices = mesh.indices();
|
let indices = mesh.indices();
|
||||||
|
|
||||||
index_offset += self.index_buffer.write(
|
index_offset +=
|
||||||
device,
|
self.index_buffer
|
||||||
encoder,
|
.write(encoder, belt, index_offset, indices);
|
||||||
belt,
|
|
||||||
index_offset,
|
|
||||||
indices,
|
|
||||||
);
|
|
||||||
|
|
||||||
match mesh {
|
match mesh {
|
||||||
Mesh::Solid { buffers, .. } => {
|
Mesh::Solid { buffers, .. } => {
|
||||||
solid_vertex_offset += self.solid.vertices.write(
|
solid_vertex_offset += self.solid.vertices.write(
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
belt,
|
belt,
|
||||||
solid_vertex_offset,
|
solid_vertex_offset,
|
||||||
|
|
@ -444,16 +440,14 @@ impl Layer {
|
||||||
);
|
);
|
||||||
|
|
||||||
solid_uniform_offset += self.solid.uniforms.write(
|
solid_uniform_offset += self.solid.uniforms.write(
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
belt,
|
belt,
|
||||||
solid_uniform_offset,
|
solid_uniform_offset,
|
||||||
&[uniforms],
|
&[uniforms],
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
Mesh::Gradient { buffers, .. } => {
|
Mesh::Gradient { buffers, .. } => {
|
||||||
gradient_vertex_offset += self.gradient.vertices.write(
|
gradient_vertex_offset += self.gradient.vertices.write(
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
belt,
|
belt,
|
||||||
gradient_vertex_offset,
|
gradient_vertex_offset,
|
||||||
|
|
@ -461,7 +455,6 @@ impl Layer {
|
||||||
);
|
);
|
||||||
|
|
||||||
gradient_uniform_offset += self.gradient.uniforms.write(
|
gradient_uniform_offset += self.gradient.uniforms.write(
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
belt,
|
belt,
|
||||||
gradient_uniform_offset,
|
gradient_uniform_offset,
|
||||||
|
|
@ -731,7 +724,7 @@ mod solid {
|
||||||
&wgpu::PipelineLayoutDescriptor {
|
&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
|
label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
|
||||||
bind_group_layouts: &[&constants_layout],
|
bind_group_layouts: &[&constants_layout],
|
||||||
push_constant_ranges: &[],
|
immediate_size: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -784,7 +777,7 @@ mod solid {
|
||||||
primitive: triangle::primitive_state(),
|
primitive: triangle::primitive_state(),
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: triangle::multisample_state(antialiasing),
|
multisample: triangle::multisample_state(antialiasing),
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -886,7 +879,7 @@ mod gradient {
|
||||||
&wgpu::PipelineLayoutDescriptor {
|
&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
|
label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
|
||||||
bind_group_layouts: &[&constants_layout],
|
bind_group_layouts: &[&constants_layout],
|
||||||
push_constant_ranges: &[],
|
immediate_size: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -949,7 +942,7 @@ mod gradient {
|
||||||
primitive: triangle::primitive_state(),
|
primitive: triangle::primitive_state(),
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: triangle::multisample_state(antialiasing),
|
multisample: triangle::multisample_state(antialiasing),
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ impl Pipeline {
|
||||||
let layout =
|
let layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::triangle::msaa pipeline layout"),
|
label: Some("iced_wgpu::triangle::msaa pipeline layout"),
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||||
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shader =
|
let shader =
|
||||||
|
|
@ -116,7 +116,7 @@ impl Pipeline {
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -177,6 +177,7 @@ impl Pipeline {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +321,6 @@ impl State {
|
||||||
0,
|
0,
|
||||||
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
||||||
.expect("non-empty ratio"),
|
.expect("non-empty ratio"),
|
||||||
device,
|
|
||||||
)
|
)
|
||||||
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
||||||
|
|
||||||
|
|
@ -351,6 +351,7 @@ impl State {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
render_pass.set_pipeline(&pipeline.raw);
|
render_pass.set_pipeline(&pipeline.raw);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
//! Display rendering results on windows.
|
//! Display rendering results on windows.
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
#[cfg(all(
|
#[cfg(wayland_platform)]
|
||||||
unix,
|
|
||||||
feature = "cctk",
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "redox")
|
|
||||||
))]
|
|
||||||
mod wayland;
|
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;
|
mod x11;
|
||||||
|
|
||||||
pub use compositor::Compositor;
|
pub use compositor::Compositor;
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,10 @@ use crate::graphics::{self, Shell, Viewport};
|
||||||
use crate::settings::{self, Settings};
|
use crate::settings::{self, Settings};
|
||||||
use crate::{Engine, Renderer};
|
use crate::{Engine, Renderer};
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(wayland_platform)]
|
||||||
unix,
|
|
||||||
feature = "cctk",
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "redox")
|
|
||||||
))]
|
|
||||||
use super::wayland::get_wayland_device_ids;
|
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 super::x11::get_x11_device_ids;
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
/// A window graphics backend for iced powered by `wgpu`.
|
/// A window graphics backend for iced powered by `wgpu`.
|
||||||
pub struct Compositor {
|
pub struct Compositor {
|
||||||
|
|
@ -65,15 +59,12 @@ impl Compositor {
|
||||||
compatible_window: Option<W>,
|
compatible_window: Option<W>,
|
||||||
shell: Shell,
|
shell: Shell,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
#[cfg(all(
|
#[cfg(wayland_platform)]
|
||||||
unix,
|
|
||||||
feature = "cctk",
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "redox")
|
|
||||||
))]
|
|
||||||
let ids = compatible_window.as_ref().and_then(|window| {
|
let ids = compatible_window.as_ref().and_then(|window| {
|
||||||
get_wayland_device_ids(window)
|
let w = get_wayland_device_ids(window);
|
||||||
.or_else(|| get_x11_device_ids(window))
|
#[cfg(feature = "x11")]
|
||||||
|
let w = w.or_else(|| get_x11_device_ids(window));
|
||||||
|
w
|
||||||
});
|
});
|
||||||
|
|
||||||
// HACK:
|
// HACK:
|
||||||
|
|
@ -82,12 +73,7 @@ impl Compositor {
|
||||||
// 3. and the user didn't set an adapter name,
|
// 3. and the user didn't set an adapter name,
|
||||||
// 4. and the user didn't request the high power pref
|
// 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
|
// => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays
|
||||||
#[cfg(all(
|
#[cfg(wayland_platform)]
|
||||||
unix,
|
|
||||||
feature = "cctk",
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "redox")
|
|
||||||
))]
|
|
||||||
if !matches!(ids, Some((0x10de, _)))
|
if !matches!(ids, Some((0x10de, _)))
|
||||||
&& std::env::var_os("__NV_PRIME_RENDER_OFFLOAD")
|
&& std::env::var_os("__NV_PRIME_RENDER_OFFLOAD")
|
||||||
.is_none_or(|var| var == "0")
|
.is_none_or(|var| var == "0")
|
||||||
|
|
@ -126,6 +112,7 @@ impl Compositor {
|
||||||
// if log::max_level() >= log::LevelFilter::Info {
|
// if log::max_level() >= log::LevelFilter::Info {
|
||||||
// let available_adapters: Vec<_> = instance
|
// let available_adapters: Vec<_> = instance
|
||||||
// .enumerate_adapters(settings.backends)
|
// .enumerate_adapters(settings.backends)
|
||||||
|
// .await
|
||||||
// .iter()
|
// .iter()
|
||||||
// .map(wgpu::Adapter::get_info)
|
// .map(wgpu::Adapter::get_info)
|
||||||
// .collect();
|
// .collect();
|
||||||
|
|
@ -146,15 +133,11 @@ impl Compositor {
|
||||||
let mut adapter = None;
|
let mut adapter = None;
|
||||||
#[cfg_attr(not(unix), allow(dead_code))]
|
#[cfg_attr(not(unix), allow(dead_code))]
|
||||||
if std::env::var_os("WGPU_ADAPTER_NAME").is_none() {
|
if std::env::var_os("WGPU_ADAPTER_NAME").is_none() {
|
||||||
#[cfg(all(
|
#[cfg(wayland_platform)]
|
||||||
unix,
|
|
||||||
feature = "cctk",
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "redox")
|
|
||||||
))]
|
|
||||||
if let Some((vendor_id, device_id)) = ids {
|
if let Some((vendor_id, device_id)) = ids {
|
||||||
adapter = instance
|
adapter = instance
|
||||||
.enumerate_adapters(settings.backends)
|
.enumerate_adapters(settings.backends)
|
||||||
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|adapter| {
|
.filter(|adapter| {
|
||||||
let info = adapter.get_info();
|
let info = adapter.get_info();
|
||||||
|
|
@ -172,6 +155,7 @@ impl Compositor {
|
||||||
} else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") {
|
} else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") {
|
||||||
adapter = instance
|
adapter = instance
|
||||||
.enumerate_adapters(settings.backends)
|
.enumerate_adapters(settings.backends)
|
||||||
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|adapter| {
|
.filter(|adapter| {
|
||||||
let info = adapter.get_info();
|
let info = adapter.get_info();
|
||||||
|
|
@ -184,6 +168,8 @@ impl Compositor {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
adapter = instance.request_adapter(&adapter_options).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let adapter = match adapter {
|
let adapter = match adapter {
|
||||||
|
|
@ -286,6 +272,14 @@ impl Compositor {
|
||||||
..limits
|
..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();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
for required_limits in limits {
|
for required_limits in limits {
|
||||||
|
|
@ -294,7 +288,7 @@ impl Compositor {
|
||||||
label: Some(
|
label: Some(
|
||||||
"iced_wgpu::window::compositor device descriptor",
|
"iced_wgpu::window::compositor device descriptor",
|
||||||
),
|
),
|
||||||
required_features: wgpu::Features::empty(),
|
required_features,
|
||||||
required_limits: required_limits.clone(),
|
required_limits: required_limits.clone(),
|
||||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
trace: wgpu::Trace::Off,
|
trace: wgpu::Trace::Off,
|
||||||
|
|
@ -352,6 +346,14 @@ pub fn present(
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
match surface.get_current_texture() {
|
match surface.get_current_texture() {
|
||||||
Ok(frame) => {
|
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
|
let view = &frame
|
||||||
.texture
|
.texture
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
@ -367,6 +369,10 @@ pub fn present(
|
||||||
on_pre_present();
|
on_pre_present();
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
|
if suboptimal {
|
||||||
|
return Err(compositor::SurfaceError::Outdated);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(unexpected_cfgs)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
|
|
@ -144,7 +146,9 @@ pub fn get_x11_device_ids<W: Window>(window: &W) -> Option<(u16, u16)> {
|
||||||
{
|
{
|
||||||
let stat =
|
let stat =
|
||||||
stat(Path::new("/dev/dri").join(device)).ok()?;
|
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);
|
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 dri3 = conn.dri3_open(root, x11rb::NONE).ok()?.reply().ok()?;
|
||||||
let device_fd = dri3.device_fd;
|
let device_fd = dri3.device_fd;
|
||||||
let stat = fstat(device_fd).ok()?;
|
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)
|
super::ids_from_dev(dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,13 @@ highlighter = ["dep:iced_highlighter"]
|
||||||
advanced = []
|
advanced = []
|
||||||
crisp = []
|
crisp = []
|
||||||
a11y = ["iced_accessibility"]
|
a11y = ["iced_accessibility"]
|
||||||
cctk = ["iced_runtime/cctk", "dep:cctk"]
|
wayland = ["iced_runtime/wayland", "dep:cctk"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_renderer.workspace = true
|
iced_renderer.workspace = true
|
||||||
iced_runtime.workspace = true
|
iced_runtime.workspace = true
|
||||||
iced_accessibility.workspace = true
|
iced_accessibility.workspace = true
|
||||||
iced_accessibility.optional = true
|
iced_accessibility.optional = true
|
||||||
cctk.workspace = true
|
|
||||||
cctk.optional = true
|
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
|
|
@ -57,3 +55,10 @@ pulldown-cmark.optional = true
|
||||||
|
|
||||||
iced_highlighter.workspace = true
|
iced_highlighter.workspace = true
|
||||||
iced_highlighter.optional = 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_hovered: bool,
|
||||||
is_pressed: bool,
|
is_pressed: bool,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
|
needs_redraw: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -368,6 +369,12 @@ where
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
if state.needs_redraw {
|
||||||
|
state.needs_redraw = false;
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
if shell.is_event_captured() {
|
if shell.is_event_captured() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1000,10 +1007,12 @@ impl operation::Focusable for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus(&mut self) {
|
fn focus(&mut self) {
|
||||||
|
self.needs_redraw |= !self.is_focused;
|
||||||
self.is_focused = true;
|
self.is_focused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unfocus(&mut self) {
|
fn unfocus(&mut self) {
|
||||||
|
self.needs_redraw |= self.is_focused;
|
||||||
self.is_focused = false;
|
self.is_focused = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,7 @@ where
|
||||||
state.raw(),
|
state.raw(),
|
||||||
crate::text::Style {
|
crate::text::Style {
|
||||||
color: style.text_color,
|
color: style.text_color,
|
||||||
|
..crate::text::Style::default()
|
||||||
},
|
},
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ use crate::core::{
|
||||||
color,
|
color,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_runtime::{Action, Task, task};
|
use iced_runtime::{Task, task};
|
||||||
|
|
||||||
/// A widget that aligns its contents inside of its boundaries.
|
/// 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>>),
|
operate: &mut dyn FnMut(&mut dyn Operation<Option<Rectangle>>),
|
||||||
) {
|
) {
|
||||||
self.depth += 1;
|
self.depth += 1;
|
||||||
self.traverse(operate);
|
operate(self);
|
||||||
self.depth -= 1;
|
self.depth -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! Keyed columns distribute content vertically while keeping continuity.
|
//! Keyed columns distribute content vertically while keeping continuity.
|
||||||
//! Distribute content vertically.
|
//! Distribute content vertically.
|
||||||
|
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -293,9 +292,12 @@ where
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.for_each(|((child, state), c_layout)| {
|
.for_each(|((child, state), c_layout)| {
|
||||||
child
|
child.as_widget_mut().operate(
|
||||||
.as_widget_mut()
|
state,
|
||||||
.operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation);
|
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +320,13 @@ where
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
{
|
{
|
||||||
child.as_widget_mut().update(
|
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,
|
viewport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -337,9 +345,13 @@ where
|
||||||
.zip(&tree.children)
|
.zip(&tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.map(|((child, tree), c_layout)| {
|
.map(|((child, tree), c_layout)| {
|
||||||
child
|
child.as_widget().mouse_interaction(
|
||||||
.as_widget()
|
tree,
|
||||||
.mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer)
|
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub use iced_renderer::graphics;
|
||||||
|
|
||||||
pub use core::widget::Id;
|
pub use core::widget::Id;
|
||||||
|
|
||||||
|
pub(crate) const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
mod pin;
|
mod pin;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
use crate::core::event::{self, Event};
|
use crate::core::event::Event;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -363,10 +363,11 @@ where
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let offset = layout.position() - Point::ORIGIN;
|
let offset = layout.position() - Point::ORIGIN;
|
||||||
|
|
||||||
self.visible_elements
|
for (element, (index, layout, tree)) in self
|
||||||
|
.visible_elements
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut state.visible_layouts)
|
.zip(&mut state.visible_layouts)
|
||||||
.map(|(element, (index, layout, tree))| {
|
{
|
||||||
element.as_widget_mut().update(
|
element.as_widget_mut().update(
|
||||||
tree,
|
tree,
|
||||||
event,
|
event,
|
||||||
|
|
@ -379,8 +380,8 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(_)) = event {
|
if let Event::Window(window::Event::RedrawRequested(_)) = event {
|
||||||
match &mut state.task {
|
match &mut state.task {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use iced_renderer::core::mouse::Click;
|
use iced_renderer::core::mouse::Click;
|
||||||
|
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ use crate::core::{
|
||||||
};
|
};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
|
use crate::DRAG_DEADBAND_DISTANCE;
|
||||||
|
|
||||||
const THICKNESS_RATIO: f32 = 25.0;
|
const THICKNESS_RATIO: f32 = 25.0;
|
||||||
|
|
||||||
/// A collection of panes distributed using either vertical or horizontal splits
|
/// 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::widget::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size,
|
self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size,
|
||||||
Vector, event,
|
Vector,
|
||||||
};
|
};
|
||||||
use crate::pane_grid::{Draggable, TitleBar};
|
use crate::pane_grid::{Draggable, TitleBar};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ where
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
overflow: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -75,6 +76,7 @@ where
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: Length::Fill,
|
height: Length::Fill,
|
||||||
position: Point::ORIGIN,
|
position: Point::ORIGIN,
|
||||||
|
overflow: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,6 +109,12 @@ where
|
||||||
self.position.y = y.into().0;
|
self.position.y = y.into().0;
|
||||||
self
|
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>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -146,7 +154,11 @@ where
|
||||||
let limits = limits.width(self.width).height(self.height);
|
let limits = limits.width(self.width).height(self.height);
|
||||||
|
|
||||||
let available =
|
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
|
let node = self
|
||||||
.content
|
.content
|
||||||
|
|
|
||||||
|
|
@ -459,6 +459,7 @@ where
|
||||||
state.raw(),
|
state.raw(),
|
||||||
crate::text::Style {
|
crate::text::Style {
|
||||||
color: style.text_color,
|
color: style.text_color,
|
||||||
|
..crate::text::Style::default()
|
||||||
},
|
},
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ use iced_runtime::core::widget::Id;
|
||||||
#[cfg(feature = "a11y")]
|
#[cfg(feature = "a11y")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
|
|
@ -248,64 +247,19 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether the user should be allowed to auto-scroll the [`Scrollable`]
|
/// Adds padding at the ends of the [`Scrollbar`]s of the [`Scrollable`].
|
||||||
/// with the middle mouse button.
|
|
||||||
///
|
///
|
||||||
/// By default, it is disabled.
|
/// The `padding` provided will be used as space at the top and bottom of a
|
||||||
pub fn auto_scroll(mut self, auto_scroll: bool) -> Self {
|
/// vertical [`Scrollbar`], and at the left and right ends of a horizontal
|
||||||
self.auto_scroll = auto_scroll;
|
/// [`Scrollbar`], when they are visible.
|
||||||
self
|
///
|
||||||
}
|
/// Unlike [`Self::spacing`], the padding does not affect the layout of the
|
||||||
|
/// [`Scrollable`].
|
||||||
/// Sets the scrollbar width of the [`Scrollbar`].
|
pub fn padding(mut self, new_padding: impl Into<Pixels>) -> Self {
|
||||||
pub fn scrollbar_width(mut self, width: impl Into<Pixels>) -> Self {
|
let padding = new_padding.into().0;
|
||||||
let width = width.into().0.max(0.0);
|
|
||||||
|
|
||||||
match &mut self.direction {
|
match &mut self.direction {
|
||||||
Direction::Horizontal(scrollbar)
|
Direction::Horizontal(scrollbar) | Direction::Vertical(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) => {
|
|
||||||
scrollbar.padding = padding;
|
scrollbar.padding = padding;
|
||||||
}
|
}
|
||||||
Direction::Both {
|
Direction::Both {
|
||||||
|
|
@ -320,6 +274,15 @@ where
|
||||||
self
|
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`].
|
/// Sets the style of this [`Scrollable`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
||||||
|
|
@ -504,9 +467,15 @@ impl Scrollbar {
|
||||||
self
|
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 {
|
pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
|
||||||
self.padding = padding.into().0.max(0.0);
|
self.padding = padding.into().0;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -676,13 +645,7 @@ where
|
||||||
let translation =
|
let translation =
|
||||||
state.translation(self.direction, bounds, content_bounds);
|
state.translation(self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
operation.scrollable(
|
operation.pre_operation(Some(&self.id));
|
||||||
Some(&self.id),
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
translation,
|
|
||||||
state,
|
|
||||||
);
|
|
||||||
|
|
||||||
operation.traverse(&mut |operation| {
|
operation.traverse(&mut |operation| {
|
||||||
self.content.as_widget_mut().operate(
|
self.content.as_widget_mut().operate(
|
||||||
|
|
@ -696,6 +659,14 @@ where
|
||||||
operation,
|
operation,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
// XXX must be done after traversing to perform DFS
|
||||||
|
operation.scrollable(
|
||||||
|
Some(&self.id),
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
translation,
|
||||||
|
state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -933,7 +904,7 @@ where
|
||||||
|
|
||||||
let had_input_method = shell.input_method().is_enabled();
|
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(
|
Event::Dnd(dnd::DndEvent::Offer(
|
||||||
id,
|
id,
|
||||||
dnd::OfferEvent::Enter {
|
dnd::OfferEvent::Enter {
|
||||||
|
|
@ -961,6 +932,16 @@ where
|
||||||
y: y + translation.y as f64,
|
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,
|
e => e,
|
||||||
};
|
};
|
||||||
self.content.as_widget_mut().update(
|
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!(
|
if matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
|
@ -996,10 +985,18 @@ where
|
||||||
| touch::Event::FingerLost { .. }
|
| touch::Event::FingerLost { .. }
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
if matches!(state.interaction, Interaction::TouchScrolling(_)) {
|
||||||
|
state.suppress_touch_hover = true;
|
||||||
|
}
|
||||||
state.interaction = Interaction::None;
|
state.interaction = Interaction::None;
|
||||||
|
state.touch_press_start = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Event::Touch(touch::Event::FingerPressed { .. }) = event {
|
||||||
|
state.touch_press_start = cursor_over_scrollable;
|
||||||
|
}
|
||||||
|
|
||||||
if shell.is_event_captured() {
|
if shell.is_event_captured() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1093,14 +1090,31 @@ where
|
||||||
Interaction::TouchScrolling(position);
|
Interaction::TouchScrolling(position);
|
||||||
}
|
}
|
||||||
touch::Event::FingerMoved { .. } => {
|
touch::Event::FingerMoved { .. } => {
|
||||||
let Interaction::TouchScrolling(
|
let Some(cursor_position) = cursor.position()
|
||||||
scroll_box_touched_at,
|
|
||||||
) = state.interaction
|
|
||||||
else {
|
else {
|
||||||
return;
|
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 {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -1322,6 +1336,14 @@ where
|
||||||
state.translation(self.direction, bounds, content_bounds);
|
state.translation(self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
let cursor = match cursor_over_scrollable {
|
let cursor = match cursor_over_scrollable {
|
||||||
|
_ if state.suppress_touch_hover
|
||||||
|
|| matches!(
|
||||||
|
state.interaction,
|
||||||
|
Interaction::TouchScrolling(_)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
mouse::Cursor::Unavailable
|
||||||
|
}
|
||||||
Some(cursor_position)
|
Some(cursor_position)
|
||||||
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
||||||
{
|
{
|
||||||
|
|
@ -1497,6 +1519,14 @@ where
|
||||||
state.translation(self.direction, bounds, content_bounds);
|
state.translation(self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
let cursor = match cursor_over_scrollable {
|
let cursor = match cursor_over_scrollable {
|
||||||
|
_ if state.suppress_touch_hover
|
||||||
|
|| matches!(
|
||||||
|
state.interaction,
|
||||||
|
Interaction::TouchScrolling(_)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
cursor.levitate() + translation
|
||||||
|
}
|
||||||
Some(cursor_position)
|
Some(cursor_position)
|
||||||
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
|
||||||
{
|
{
|
||||||
|
|
@ -2022,6 +2052,8 @@ struct State {
|
||||||
last_notified: Option<Viewport>,
|
last_notified: Option<Viewport>,
|
||||||
last_scrolled: Option<Instant>,
|
last_scrolled: Option<Instant>,
|
||||||
is_scrollbar_visible: bool,
|
is_scrollbar_visible: bool,
|
||||||
|
touch_press_start: Option<Point>,
|
||||||
|
suppress_touch_hover: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -2047,6 +2079,8 @@ impl Default for State {
|
||||||
last_notified: None,
|
last_notified: None,
|
||||||
last_scrolled: None,
|
last_scrolled: None,
|
||||||
is_scrollbar_visible: true,
|
is_scrollbar_visible: true,
|
||||||
|
touch_press_start: None,
|
||||||
|
suppress_touch_hover: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2349,13 +2383,16 @@ impl Scrollbars {
|
||||||
let total_scrollbar_width =
|
let total_scrollbar_width =
|
||||||
width.max(scroller_width) + 2.0 * margin;
|
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
|
// Total bounds of the scrollbar + margin + scroller width
|
||||||
let total_scrollbar_bounds = Rectangle {
|
let total_scrollbar_bounds = Rectangle {
|
||||||
x: bounds.x + bounds.width - total_scrollbar_width,
|
x: bounds.x + bounds.width - total_scrollbar_width,
|
||||||
y: bounds.y + padding,
|
y: bounds.y + padding,
|
||||||
width: total_scrollbar_width,
|
width: total_scrollbar_width,
|
||||||
height: (bounds.height - x_scrollbar_height - 2.0 * padding)
|
height: scrollbar_height,
|
||||||
.max(0.0),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bounds of just the scrollbar
|
// Bounds of just the scrollbar
|
||||||
|
|
@ -2365,8 +2402,7 @@ impl Scrollbars {
|
||||||
- width / 2.0,
|
- width / 2.0,
|
||||||
y: bounds.y + padding,
|
y: bounds.y + padding,
|
||||||
width,
|
width,
|
||||||
height: (bounds.height - x_scrollbar_height - 2.0 * padding)
|
height: scrollbar_height,
|
||||||
.max(0.0),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let ratio = bounds.height / content_bounds.height;
|
let ratio = bounds.height / content_bounds.height;
|
||||||
|
|
@ -2423,12 +2459,15 @@ impl Scrollbars {
|
||||||
let total_scrollbar_height =
|
let total_scrollbar_height =
|
||||||
width.max(scroller_width) + 2.0 * margin;
|
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
|
// Total bounds of the scrollbar + margin + scroller width
|
||||||
let total_scrollbar_bounds = Rectangle {
|
let total_scrollbar_bounds = Rectangle {
|
||||||
x: bounds.x + padding,
|
x: bounds.x + padding,
|
||||||
y: bounds.y + bounds.height - total_scrollbar_height,
|
y: bounds.y + bounds.height - total_scrollbar_height,
|
||||||
width: (bounds.width - scrollbar_y_width - 2.0 * padding)
|
width: scrollbar_width,
|
||||||
.max(0.0),
|
|
||||||
height: total_scrollbar_height,
|
height: total_scrollbar_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2438,8 +2477,7 @@ impl Scrollbars {
|
||||||
y: bounds.y + bounds.height
|
y: bounds.y + bounds.height
|
||||||
- total_scrollbar_height / 2.0
|
- total_scrollbar_height / 2.0
|
||||||
- width / 2.0,
|
- width / 2.0,
|
||||||
width: (bounds.width - scrollbar_y_width - 2.0 * padding)
|
width: scrollbar_width,
|
||||||
.max(0.0),
|
|
||||||
height: width,
|
height: width,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ where
|
||||||
height: f32,
|
height: f32,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
status: Option<Status>,
|
status: Option<Status>,
|
||||||
|
handle_width: f32,
|
||||||
|
handle_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
|
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
|
||||||
|
|
@ -119,7 +121,13 @@ where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
/// The default height of a [`Slider`].
|
/// 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`].
|
/// Creates a new [`Slider`].
|
||||||
///
|
///
|
||||||
|
|
@ -165,6 +173,8 @@ where
|
||||||
height: Self::DEFAULT_HEIGHT,
|
height: Self::DEFAULT_HEIGHT,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
status: None,
|
status: None,
|
||||||
|
handle_width: Self::DEFAULT_MAX_HANDLE_WIDTH,
|
||||||
|
handle_height: Self::DEFAULT_MAX_HANDLE_HEIGHT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +217,24 @@ where
|
||||||
self
|
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`].
|
/// Sets the step size of the [`Slider`].
|
||||||
pub fn step(mut self, step: impl Into<T>) -> Self {
|
pub fn step(mut self, step: impl Into<T>) -> Self {
|
||||||
self.step = step.into();
|
self.step = step.into();
|
||||||
|
|
@ -316,15 +344,22 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
let mut update = || {
|
let mut update = || {
|
||||||
let current_value = self.value;
|
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 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 {
|
if cursor_position.x <= bounds.x {
|
||||||
Some(*self.range.start())
|
Some(*self.range.start())
|
||||||
|
|
@ -417,6 +452,8 @@ where
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(
|
Event::Mouse(mouse::Event::ButtonReleased(
|
||||||
mouse::Button::Left,
|
mouse::Button::Left,
|
||||||
))
|
))
|
||||||
|
| Event::Mouse(mouse::Event::CursorLeft)
|
||||||
|
| Event::Mouse(mouse::Event::CursorEntered)
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||||
if state.is_dragging {
|
if state.is_dragging {
|
||||||
|
|
@ -429,15 +466,22 @@ where
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
if state.is_dragging {
|
if state.is_dragging {
|
||||||
let _ = cursor
|
let position = cursor.land().position();
|
||||||
.land()
|
let outside = position.is_none_or(|position| {
|
||||||
.position()
|
!viewport.contains(position)
|
||||||
.and_then(locate)
|
});
|
||||||
.map(change);
|
|
||||||
|
|
||||||
|
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();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||||
if state.keyboard_modifiers.control() =>
|
if state.keyboard_modifiers.control() =>
|
||||||
{
|
{
|
||||||
|
|
@ -509,54 +553,30 @@ where
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_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 =
|
let style =
|
||||||
theme.style(&self.class, self.status.unwrap_or(Status::Active));
|
theme.style(&self.class, self.status.unwrap_or(Status::Active));
|
||||||
|
|
||||||
let border_width = style
|
let (mut handle_width, mut handle_height, handle_border_radius) =
|
||||||
.handle
|
|
||||||
.border_width
|
|
||||||
.min(bounds.height / 2.0)
|
|
||||||
.min(bounds.width / 2.0);
|
|
||||||
|
|
||||||
let (handle_width, handle_height, handle_border_radius) =
|
|
||||||
match style.handle.shape {
|
match style.handle.shape {
|
||||||
HandleShape::Circle { radius } => {
|
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))
|
(radius * 2.0, radius * 2.0, Radius::from(radius))
|
||||||
}
|
}
|
||||||
HandleShape::Rectangle {
|
HandleShape::Rectangle {
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
border_radius,
|
border_radius,
|
||||||
} => {
|
} => (f32::from(width), f32::from(height), 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],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handle_width = handle_width.min(self.handle_width);
|
||||||
|
handle_height = handle_height.min(self.handle_height);
|
||||||
|
|
||||||
let value = self.value.into() as f32;
|
let value = self.value.into() as f32;
|
||||||
let (range_start, range_end) = {
|
let (range_start, range_end) = {
|
||||||
let (start, end) = self.range.clone().into_inner();
|
let (start, end) = self.range.clone().into_inner();
|
||||||
|
|
@ -567,8 +587,7 @@ where
|
||||||
let offset = if range_start >= range_end {
|
let offset = if range_start >= range_end {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
(bounds.width - handle_width) * (value - range_start)
|
bounds.width * (value - range_start) / (range_end - range_start)
|
||||||
/ (range_end - range_start)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let rail_y = bounds.y + bounds.height / 2.0;
|
let rail_y = bounds.y + bounds.height / 2.0;
|
||||||
|
|
@ -590,7 +609,8 @@ where
|
||||||
x: bounds.x + offset,
|
x: bounds.x + offset,
|
||||||
y: rail_y + 6.0,
|
y: rail_y + 6.0,
|
||||||
width: BREAKPOINT_WIDTH,
|
width: BREAKPOINT_WIDTH,
|
||||||
height: 8.0,
|
height: 8.0_f32
|
||||||
|
.min(bounds.y + bounds.height - (rail_y + 6.0)),
|
||||||
},
|
},
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
|
|
@ -607,9 +627,9 @@ where
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
y: rail_y - style.rail.width / 2.0,
|
y: rail_y - (style.rail.width).min(bounds.height) / 2.0,
|
||||||
width: offset + handle_width / 2.0,
|
width: offset,
|
||||||
height: style.rail.width,
|
height: (style.rail.width).min(bounds.height),
|
||||||
},
|
},
|
||||||
border: style.rail.border,
|
border: style.rail.border,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
|
|
@ -622,10 +642,10 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + offset + handle_width / 2.0,
|
x: bounds.x + offset,
|
||||||
y: rail_y - style.rail.width / 2.0,
|
y: rail_y - (style.rail.width).min(bounds.height) / 2.0,
|
||||||
width: bounds.width - offset - handle_width / 2.0,
|
width: bounds.width - offset,
|
||||||
height: style.rail.width,
|
height: (style.rail.width).min(bounds.height),
|
||||||
},
|
},
|
||||||
border: style.rail.border,
|
border: style.rail.border,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
|
|
@ -637,7 +657,7 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + offset,
|
x: bounds.x + offset - handle_width / 2.0,
|
||||||
y: rail_y - (handle_height / 2.0),
|
y: rail_y - (handle_height / 2.0),
|
||||||
width: handle_width,
|
width: handle_width,
|
||||||
height: handle_height,
|
height: handle_height,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
//! Display content on top of other content.
|
//! Display content on top of other content.
|
||||||
|
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,10 @@ where
|
||||||
{
|
{
|
||||||
let color = color.map(Into::into);
|
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.
|
/// Sets the default style class of the [`Rich`] text.
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,9 @@ pub struct State<Highlighter: text::Highlighter> {
|
||||||
highlighter: RefCell<Highlighter>,
|
highlighter: RefCell<Highlighter>,
|
||||||
highlighter_settings: Highlighter::Settings,
|
highlighter_settings: Highlighter::Settings,
|
||||||
highlighter_format_address: usize,
|
highlighter_format_address: usize,
|
||||||
|
context_menu_position: Option<Point>,
|
||||||
|
clipboard_has_text: bool,
|
||||||
|
pending_edit: Option<Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -579,6 +582,14 @@ impl<Highlighter: text::Highlighter> State<Highlighter> {
|
||||||
pub fn is_focused(&self) -> bool {
|
pub fn is_focused(&self) -> bool {
|
||||||
self.focus.is_some()
|
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
|
impl<Highlighter: text::Highlighter> operation::Focusable
|
||||||
|
|
@ -593,7 +604,7 @@ impl<Highlighter: text::Highlighter> operation::Focusable
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unfocus(&mut self) {
|
fn unfocus(&mut self) {
|
||||||
self.focus = None;
|
self.clear_focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -621,6 +632,9 @@ where
|
||||||
)),
|
)),
|
||||||
highlighter_settings: self.highlighter_settings.clone(),
|
highlighter_settings: self.highlighter_settings.clone(),
|
||||||
highlighter_format_address: self.highlighter_format as usize,
|
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>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_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 {
|
let Some(on_edit) = self.on_edit.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -1223,6 +1291,23 @@ impl<Message> Binding<Message> {
|
||||||
return Some(binding);
|
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")]
|
#[cfg(target_os = "macos")]
|
||||||
let modified_key =
|
let modified_key =
|
||||||
convert_macos_shortcut(&key, modifiers).unwrap_or(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))
|
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::container;
|
||||||
|
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ use std::borrow::Cow;
|
||||||
use iced_runtime::core::border::Radius;
|
use iced_runtime::core::border::Radius;
|
||||||
|
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::border;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
|
@ -483,6 +482,7 @@ where
|
||||||
state.raw(),
|
state.raw(),
|
||||||
crate::text::Style {
|
crate::text::Style {
|
||||||
color: style.text_color,
|
color: style.text_color,
|
||||||
|
..crate::text::Style::default()
|
||||||
},
|
},
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
@ -509,7 +509,7 @@ where
|
||||||
|
|
||||||
let space = style.handle_margin;
|
let space = style.handle_margin;
|
||||||
|
|
||||||
let toggler_background_bounds = Rectangle {
|
let _toggler_background_bounds = Rectangle {
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
y: bounds.y,
|
y: bounds.y,
|
||||||
width: bounds.width,
|
width: bounds.width,
|
||||||
|
|
@ -529,7 +529,7 @@ where
|
||||||
style.background,
|
style.background,
|
||||||
);
|
);
|
||||||
|
|
||||||
let padding = (style.padding_ratio * bounds.height).round();
|
let _padding = (style.padding_ratio * bounds.height).round();
|
||||||
let toggler_foreground_bounds = Rectangle {
|
let toggler_foreground_bounds = Rectangle {
|
||||||
x: bounds.x
|
x: bounds.x
|
||||||
+ if self.is_toggled {
|
+ if self.is_toggled {
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let is_dragging = state.is_dragging;
|
let is_dragging = state.is_dragging;
|
||||||
|
|
@ -354,6 +354,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
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::FingerLifted { .. })
|
||||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||||
if is_dragging {
|
if is_dragging {
|
||||||
|
|
@ -366,12 +368,22 @@ where
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
if is_dragging {
|
if is_dragging {
|
||||||
let _ =
|
let position = cursor.land().position();
|
||||||
cursor.land().position().and_then(locate).map(change);
|
let outside = position.is_none_or(|position| {
|
||||||
|
!viewport.contains(position)
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||||
if state.keyboard_modifiers.control() =>
|
if state.keyboard_modifiers.control() =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,18 @@ keywords.workspace = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[features]
|
[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"]
|
default = ["x11", "wayland-dlopen"]
|
||||||
debug = ["iced_debug/enable"]
|
debug = ["iced_debug/enable"]
|
||||||
sysinfo = ["dep:sysinfo"]
|
sysinfo = ["dep:sysinfo"]
|
||||||
|
|
@ -26,17 +37,6 @@ program = []
|
||||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||||
a11y = ["iced_accessibility", "iced_runtime/a11y"]
|
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.
|
# Prevents multiple separate process instances.
|
||||||
single-instance = []
|
single-instance = []
|
||||||
|
|
||||||
|
|
@ -64,7 +64,8 @@ iced_debug.workspace = true
|
||||||
iced_program.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.workspace = true
|
||||||
mundy.optional = true
|
mundy.optional = true
|
||||||
|
|
||||||
|
|
@ -89,3 +90,6 @@ winapi.workspace = true
|
||||||
web-sys.workspace = true
|
web-sys.workspace = true
|
||||||
web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
|
web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
|
||||||
wasm-bindgen-futures.workspace = true
|
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 cursor_icon::CursorIcon;
|
||||||
use winit::window::ResizeDirection;
|
use winit::window::ResizeDirection;
|
||||||
|
|
||||||
|
|
@ -24,6 +26,9 @@ const DRAG_RESIZE_SUPPORTED: bool = true;
|
||||||
const DRAG_RESIZE_SUPPORTED: bool = false;
|
const DRAG_RESIZE_SUPPORTED: bool = false;
|
||||||
|
|
||||||
/// If supported by winit, returns a closure that implements cursor resize support.
|
/// 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(
|
pub fn event_func(
|
||||||
window: &dyn winit::window::Window,
|
window: &dyn winit::window::Window,
|
||||||
border_size: f64,
|
border_size: f64,
|
||||||
|
|
@ -32,6 +37,7 @@ pub fn event_func(
|
||||||
dyn FnMut(
|
dyn FnMut(
|
||||||
&dyn winit::window::Window,
|
&dyn winit::window::Window,
|
||||||
&winit::event::WindowEvent,
|
&winit::event::WindowEvent,
|
||||||
|
mouse::Interaction,
|
||||||
) -> bool,
|
) -> bool,
|
||||||
>,
|
>,
|
||||||
> {
|
> {
|
||||||
|
|
@ -41,7 +47,8 @@ pub fn event_func(
|
||||||
|
|
||||||
Some(Box::new(
|
Some(Box::new(
|
||||||
move |window: &dyn winit::window::Window,
|
move |window: &dyn winit::window::Window,
|
||||||
window_event: &winit::event::WindowEvent|
|
window_event: &winit::event::WindowEvent,
|
||||||
|
ui_interaction: mouse::Interaction|
|
||||||
-> bool {
|
-> bool {
|
||||||
// Keep track of border resize state and set cursor icon when in range
|
// Keep track of border resize state and set cursor icon when in range
|
||||||
match window_event {
|
match window_event {
|
||||||
|
|
@ -49,21 +56,31 @@ pub fn event_func(
|
||||||
position,
|
position,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if !window.is_decorated() {
|
let location = if is_resizable(window) {
|
||||||
let location = cursor_resize_direction(
|
cursor_resize_direction(
|
||||||
window.surface_size(),
|
window.surface_size(),
|
||||||
*position,
|
*position,
|
||||||
border_size,
|
border_size,
|
||||||
);
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
if location != cursor_prev_resize_direction {
|
if location != cursor_prev_resize_direction {
|
||||||
window.set_cursor(
|
match location {
|
||||||
resize_direction_cursor_icon(location)
|
Some(direction) => window.set_cursor(
|
||||||
|
resize_direction_cursor_icon(direction)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
),
|
||||||
|
None => restore_cursor(window, ui_interaction),
|
||||||
|
}
|
||||||
cursor_prev_resize_direction = location;
|
cursor_prev_resize_direction = location;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
winit::event::WindowEvent::PointerLeft { .. } => {
|
||||||
|
if cursor_prev_resize_direction.take().is_some() {
|
||||||
|
restore_cursor(window, ui_interaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::PointerButton {
|
winit::event::WindowEvent::PointerButton {
|
||||||
state: winit::event::ElementState::Pressed,
|
state: winit::event::ElementState::Pressed,
|
||||||
|
|
@ -77,7 +94,9 @@ pub fn event_func(
|
||||||
primary: true,
|
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);
|
let _res = window.drag_resize_window(direction);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -93,12 +112,29 @@ 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.
|
/// Get the cursor icon that corresponds to the resize direction.
|
||||||
fn resize_direction_cursor_icon(
|
fn resize_direction_cursor_icon(
|
||||||
resize_direction: Option<ResizeDirection>,
|
resize_direction: ResizeDirection,
|
||||||
) -> CursorIcon {
|
) -> CursorIcon {
|
||||||
match resize_direction {
|
match resize_direction {
|
||||||
Some(resize_direction) => match resize_direction {
|
|
||||||
ResizeDirection::East => CursorIcon::EResize,
|
ResizeDirection::East => CursorIcon::EResize,
|
||||||
ResizeDirection::North => CursorIcon::NResize,
|
ResizeDirection::North => CursorIcon::NResize,
|
||||||
ResizeDirection::NorthEast => CursorIcon::NeResize,
|
ResizeDirection::NorthEast => CursorIcon::NeResize,
|
||||||
|
|
@ -107,8 +143,6 @@ fn resize_direction_cursor_icon(
|
||||||
ResizeDirection::SouthEast => CursorIcon::SeResize,
|
ResizeDirection::SouthEast => CursorIcon::SeResize,
|
||||||
ResizeDirection::SouthWest => CursorIcon::SwResize,
|
ResizeDirection::SouthWest => CursorIcon::SwResize,
|
||||||
ResizeDirection::West => CursorIcon::WResize,
|
ResizeDirection::West => CursorIcon::WResize,
|
||||||
},
|
|
||||||
None => CursorIcon::Default,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ impl Clipboard {
|
||||||
})
|
})
|
||||||
.unwrap_or(State::Unavailable);
|
.unwrap_or(State::Unavailable);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(wayland_platform)]
|
||||||
if let State::Connected { clipboard, .. } = &state {
|
if let State::Connected { clipboard, .. } = &state {
|
||||||
clipboard.init_dnd(Box::new(sender));
|
clipboard.init_dnd(Box::new(sender));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,33 @@ use winit::event::Force;
|
||||||
use winit::icon::IconProvider;
|
use winit::icon::IconProvider;
|
||||||
use winit::keyboard::SmolStr;
|
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`.
|
/// Converts some [`window::Settings`] into some `WindowAttributes` from `winit`.
|
||||||
pub fn window_attributes(
|
pub fn window_attributes(
|
||||||
settings: window::Settings,
|
settings: window::Settings,
|
||||||
|
|
@ -182,12 +209,19 @@ pub fn window_attributes(
|
||||||
{
|
{
|
||||||
use winit::platform::wayland::WindowAttributesWayland;
|
use winit::platform::wayland::WindowAttributesWayland;
|
||||||
|
|
||||||
attributes = attributes.with_platform_attributes(Box::new(
|
let mut wayland_attributes = WindowAttributesWayland::default()
|
||||||
WindowAttributesWayland::default().with_name(
|
.with_name(
|
||||||
&settings.platform_specific.application_id,
|
&settings.platform_specific.application_id,
|
||||||
&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,
|
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,
|
_ => None,
|
||||||
},
|
},
|
||||||
// TODO how to handle this for Touch? Is it different than a press? Need to double check winit impl
|
// 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 {
|
WindowEvent::PointerButton {
|
||||||
button: ButtonSource::Touch { finger_id, force },
|
button: ButtonSource::Touch { finger_id, force },
|
||||||
state,
|
state,
|
||||||
|
|
@ -387,19 +446,19 @@ pub fn window_event(
|
||||||
self::modifiers(new_modifiers.state()),
|
self::modifiers(new_modifiers.state()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
WindowEvent::Ime(event) => Some(Event::InputMethod(match event {
|
WindowEvent::Ime(event) => match event {
|
||||||
Ime::Enabled => input_method::Event::Opened,
|
Ime::Enabled => Some(input_method::Event::Opened),
|
||||||
Ime::Preedit(content, size) => input_method::Event::Preedit(
|
Ime::Preedit(content, size) => Some(input_method::Event::Preedit(
|
||||||
content,
|
content,
|
||||||
size.map(|(start, end)| start..end),
|
size.map(|(start, end)| start..end),
|
||||||
),
|
)),
|
||||||
Ime::Commit(content) => input_method::Event::Commit(content),
|
Ime::Commit(content) => Some(input_method::Event::Commit(content)),
|
||||||
Ime::Disabled => input_method::Event::Closed,
|
Ime::Disabled => Some(input_method::Event::Closed),
|
||||||
Ime::DeleteSurrounding {
|
// Upstream iced depends on the winit version which doesn't support
|
||||||
before_bytes,
|
// this kind of ime event.
|
||||||
after_bytes,
|
Ime::DeleteSurrounding { .. } => None,
|
||||||
} => todo!(),
|
}
|
||||||
})),
|
.map(Event::InputMethod),
|
||||||
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
|
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
|
||||||
window::Event::Focused
|
window::Event::Focused
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -424,7 +483,7 @@ pub fn window_event(
|
||||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||||
Some(Event::Window(window::Event::Rescaled(scale_factor as f32)))
|
Some(Event::Window(window::Event::Rescaled(scale_factor as f32)))
|
||||||
}
|
}
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
WindowEvent::SuggestedBounds(bounds) => {
|
WindowEvent::SuggestedBounds(bounds) => {
|
||||||
let size = bounds.map(|bounds| {
|
let size = bounds.map(|bounds| {
|
||||||
let size = bounds.to_logical(scale_factor);
|
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 => {
|
WindowEvent::WindowStateChanged => {
|
||||||
use cctk::sctk::reexports::csd_frame::WindowState;
|
use cctk::sctk::reexports::csd_frame::WindowState;
|
||||||
use winit::platform::wayland::WindowExtWayland;
|
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(
|
fn winit_native_key_code(
|
||||||
keycode: keyboard::key::NativeCode,
|
keycode: keyboard::key::NativeCode,
|
||||||
) -> winit::keyboard::NativeKeyCode {
|
) -> winit::keyboard::NativeKeyCode {
|
||||||
|
|
@ -1515,7 +1574,7 @@ fn winit_native_key_code(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconstruct the raw keycode
|
/// 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> {
|
pub fn physical_to_scancode(physical: keyboard::key::Physical) -> Option<u32> {
|
||||||
let Some(physical_key) = (match physical {
|
let Some(physical_key) = (match physical {
|
||||||
keyboard::key::Physical::Code(code) => {
|
keyboard::key::Physical::Code(code) => {
|
||||||
|
|
|
||||||
231
winit/src/lib.rs
231
winit/src/lib.rs
|
|
@ -40,12 +40,6 @@ pub mod clipboard;
|
||||||
pub mod conversion;
|
pub mod conversion;
|
||||||
pub mod platform_specific;
|
pub mod platform_specific;
|
||||||
|
|
||||||
#[cfg(feature = "program")]
|
|
||||||
pub mod program;
|
|
||||||
|
|
||||||
#[cfg(feature = "system")]
|
|
||||||
pub mod system;
|
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
@ -53,7 +47,6 @@ mod window;
|
||||||
pub use clipboard::Clipboard;
|
pub use clipboard::Clipboard;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use proxy::Proxy;
|
pub use proxy::Proxy;
|
||||||
use winit::dpi::LogicalSize;
|
|
||||||
use winit::dpi::PhysicalPosition;
|
use winit::dpi::PhysicalPosition;
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
|
|
||||||
|
|
@ -76,6 +69,8 @@ use crate::runtime::user_interface::{self, UserInterface};
|
||||||
use crate::runtime::{Action, Task};
|
use crate::runtime::{Action, Task};
|
||||||
|
|
||||||
use program::Program;
|
use program::Program;
|
||||||
|
#[cfg(feature = "program")]
|
||||||
|
pub use program::Program;
|
||||||
use window::WindowManager;
|
use window::WindowManager;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
@ -98,10 +93,10 @@ where
|
||||||
|
|
||||||
let event_loop = EventLoop::new().expect("Create event loop");
|
let event_loop = EventLoop::new().expect("Create event loop");
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
let is_wayland =
|
let is_wayland =
|
||||||
winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop);
|
winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop);
|
||||||
#[cfg(not(all(feature = "cctk", target_os = "linux")))]
|
#[cfg(not(wayland_platform))]
|
||||||
let is_wayland = false;
|
let is_wayland = false;
|
||||||
|
|
||||||
// TODO this is new..
|
// TODO this is new..
|
||||||
|
|
@ -192,10 +187,14 @@ where
|
||||||
receiver: mpsc::UnboundedReceiver<Control>,
|
receiver: mpsc::UnboundedReceiver<Control>,
|
||||||
error: Option<Error>,
|
error: Option<Error>,
|
||||||
system_theme: Option<oneshot::Sender<theme::Mode>>,
|
system_theme: Option<oneshot::Sender<theme::Mode>>,
|
||||||
|
#[cfg(feature = "a11y")]
|
||||||
control_sender: mpsc::UnboundedSender<Control>,
|
control_sender: mpsc::UnboundedSender<Control>,
|
||||||
|
|
||||||
#[cfg(feature = "a11y")]
|
#[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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
is_booted: std::rc::Rc<std::cell::RefCell<bool>>,
|
is_booted: std::rc::Rc<std::cell::RefCell<bool>>,
|
||||||
|
|
@ -215,6 +214,7 @@ where
|
||||||
id: settings.id,
|
id: settings.id,
|
||||||
sender: event_sender,
|
sender: event_sender,
|
||||||
receiver: control_receiver,
|
receiver: control_receiver,
|
||||||
|
#[cfg(feature = "a11y")]
|
||||||
control_sender: control_sender.clone(),
|
control_sender: control_sender.clone(),
|
||||||
error: None,
|
error: None,
|
||||||
system_theme: Some(system_theme_sender),
|
system_theme: Some(system_theme_sender),
|
||||||
|
|
@ -263,7 +263,7 @@ where
|
||||||
| winit::event::WindowEvent::Moved(_)
|
| winit::event::WindowEvent::Moved(_)
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
if matches!(event, WindowEvent::RedrawRequested) {
|
if matches!(event, WindowEvent::RedrawRequested) {
|
||||||
for id in
|
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")]
|
#[cfg(feature = "a11y")]
|
||||||
self.init_adapter(event_loop, id, window.clone());
|
self.init_adapter(
|
||||||
|
event_loop,
|
||||||
|
id,
|
||||||
|
window.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
self.process_event(
|
self.process_event(
|
||||||
event_loop,
|
event_loop,
|
||||||
|
|
@ -572,11 +578,10 @@ where
|
||||||
.expect("Send event");
|
.expect("Send event");
|
||||||
}
|
}
|
||||||
Control::Winit(id, e) => {
|
Control::Winit(id, e) => {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
if matches!(e, WindowEvent::RedrawRequested)
|
if matches!(e, WindowEvent::RedrawRequested)
|
||||||
{
|
{
|
||||||
|
|
||||||
for id in crate::subsurface_widget::subsurface_ids(id) {
|
for id in crate::subsurface_widget::subsurface_ids(id) {
|
||||||
_ = self.sender
|
_ = self.sender
|
||||||
.unbounded_send(Event::Winit(
|
.unbounded_send(Event::Winit(
|
||||||
|
|
@ -619,7 +624,7 @@ where
|
||||||
#[cfg(feature = "a11y")]
|
#[cfg(feature = "a11y")]
|
||||||
Control::Cleanup(id) => {
|
Control::Cleanup(id) => {
|
||||||
_ = self.adapters.remove(&id);
|
_ = self.adapters.remove(&id);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
break;
|
break;
|
||||||
|
|
@ -634,19 +639,21 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "a11y")]
|
#[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>) {
|
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 crate::a11y::*;
|
||||||
use iced_accessibility::accesskit::{
|
use iced_accessibility::accesskit::{
|
||||||
ActivationHandler, Node, NodeId, Role,
|
ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
|
||||||
Tree, TreeUpdate,
|
|
||||||
};
|
};
|
||||||
use iced_accessibility::accesskit_winit::Adapter;
|
use iced_accessibility::accesskit_winit::Adapter;
|
||||||
|
|
||||||
let node_id =
|
let node_id = iced_runtime::core::id::window_node_id();
|
||||||
iced_runtime::core::id::window_node_id();
|
|
||||||
|
|
||||||
let activation_handler =
|
let activation_handler = WinitActivationHandler {
|
||||||
WinitActivationHandler {
|
|
||||||
proxy: self.control_sender.clone(),
|
proxy: self.control_sender.clone(),
|
||||||
title: String::new(),
|
title: String::new(),
|
||||||
};
|
};
|
||||||
|
|
@ -656,8 +663,7 @@ where
|
||||||
proxy: self.control_sender.clone(),
|
proxy: self.control_sender.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let deactivation_handler =
|
let deactivation_handler = WinitDeactivationHandler {
|
||||||
WinitDeactivationHandler {
|
|
||||||
proxy: self.control_sender.clone(),
|
proxy: self.control_sender.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -674,8 +680,6 @@ where
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -770,7 +774,7 @@ async fn run_instance<P>(
|
||||||
|
|
||||||
let mut platform_specific_handler =
|
let mut platform_specific_handler =
|
||||||
crate::platform_specific::PlatformSpecific::default();
|
crate::platform_specific::PlatformSpecific::default();
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
if is_wayland {
|
if is_wayland {
|
||||||
platform_specific_handler = platform_specific_handler.with_wayland(
|
platform_specific_handler = platform_specific_handler.with_wayland(
|
||||||
control_sender.clone(),
|
control_sender.clone(),
|
||||||
|
|
@ -838,6 +842,30 @@ async fn run_instance<P>(
|
||||||
|
|
||||||
log::info!("System theme: {system_theme:?}");
|
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 {
|
'next_event: loop {
|
||||||
// Empty the queue if possible
|
// Empty the queue if possible
|
||||||
let event = if let Ok(event) = event_receiver.try_recv() {
|
let event = if let Ok(event) = event_receiver.try_recv() {
|
||||||
|
|
@ -857,13 +885,13 @@ async fn run_instance<P>(
|
||||||
exit_on_close_request,
|
exit_on_close_request,
|
||||||
make_visible,
|
make_visible,
|
||||||
on_open,
|
on_open,
|
||||||
resize_border
|
resize_border,
|
||||||
} => {
|
} => {
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
|
||||||
platform_specific_handler.send_wayland(
|
platform_specific_handler.send_wayland(
|
||||||
platform_specific::Action::TrackWindow(window.clone(), id),
|
platform_specific::Action::TrackWindow(window.clone(), id),
|
||||||
);
|
);
|
||||||
|
if compositor.is_none() {
|
||||||
match create_compositor::<P>(
|
match create_compositor::<P>(
|
||||||
window.clone(),
|
window.clone(),
|
||||||
CreateCompositor {
|
CreateCompositor {
|
||||||
|
|
@ -888,6 +916,7 @@ async fn run_instance<P>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let window_theme = window
|
let window_theme = window
|
||||||
.theme()
|
.theme()
|
||||||
|
|
@ -913,7 +942,7 @@ async fn run_instance<P>(
|
||||||
compositor,
|
compositor,
|
||||||
exit_on_close_request,
|
exit_on_close_request,
|
||||||
system_theme,
|
system_theme,
|
||||||
resize_border
|
resize_border,
|
||||||
);
|
);
|
||||||
|
|
||||||
window.raw.set_theme(conversion::window_theme(
|
window.raw.set_theme(conversion::window_theme(
|
||||||
|
|
@ -999,22 +1028,7 @@ async fn run_instance<P>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::UserEvent(action) => {
|
Event::UserEvent(action) => {
|
||||||
let exited = run_action(
|
let exited = run_action!(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,
|
|
||||||
);
|
|
||||||
if exited {
|
if exited {
|
||||||
runtime.track(None.into_iter());
|
runtime.track(None.into_iter());
|
||||||
}
|
}
|
||||||
|
|
@ -1042,10 +1056,10 @@ async fn run_instance<P>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// XX must force update to corner radius before the surface is committed.
|
// 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()
|
if (window.surface_version != window.state.surface_version()
|
||||||
|| window.logical_size() != window.state.logical_size()
|
|| window.logical_size() != window.state.logical_size())
|
||||||
) && !crate::subsurface_widget::is_subsurface(window_id)
|
&& !crate::subsurface_widget::is_subsurface(window_id)
|
||||||
{
|
{
|
||||||
platform_specific_handler.send_wayland(
|
platform_specific_handler.send_wayland(
|
||||||
platform_specific::Action::ResizeWindow(id),
|
platform_specific::Action::ResizeWindow(id),
|
||||||
|
|
@ -1156,22 +1170,7 @@ async fn run_instance<P>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = run_action(
|
_ = run_action!(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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (window_id, window) in window_manager.iter_mut() {
|
for (window_id, window) in window_manager.iter_mut() {
|
||||||
|
|
@ -1326,13 +1325,26 @@ async fn run_instance<P>(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// Initiates a drag resize window state when found.
|
// Initiates a drag resize window state when found.
|
||||||
if let Some(func) =
|
if let Some(func) = window.drag_resize_window_func.as_mut() {
|
||||||
window.drag_resize_window_func.as_mut()
|
if func(
|
||||||
{
|
window.raw.as_ref(),
|
||||||
if func(window.raw.as_ref(), &event) {
|
&event,
|
||||||
|
window.mouse_interaction,
|
||||||
|
) {
|
||||||
continue;
|
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 {
|
match event {
|
||||||
winit::event::WindowEvent::SurfaceResized(_) => {
|
winit::event::WindowEvent::SurfaceResized(_) => {
|
||||||
window.raw.request_redraw();
|
window.raw.request_redraw();
|
||||||
|
|
@ -1354,22 +1366,9 @@ async fn run_instance<P>(
|
||||||
if matches!(event, winit::event::WindowEvent::CloseRequested)
|
if matches!(event, winit::event::WindowEvent::CloseRequested)
|
||||||
&& window.exit_on_close_request
|
&& window.exit_on_close_request
|
||||||
{
|
{
|
||||||
_ = run_action(
|
_ = run_action!(Action::Window(
|
||||||
Action::Window(runtime::window::Action::Close(id)),
|
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,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
window.state.update(&program, window.raw.as_ref(), &event);
|
window.state.update(&program, window.raw.as_ref(), &event);
|
||||||
|
|
||||||
|
|
@ -1411,8 +1410,7 @@ async fn run_instance<P>(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let no_window_events = window_events.is_empty();
|
#[cfg(wayland_platform)]
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
|
||||||
window_events.push(core::Event::PlatformSpecific(
|
window_events.push(core::Event::PlatformSpecific(
|
||||||
core::event::PlatformSpecific::Wayland(
|
core::event::PlatformSpecific::Wayland(
|
||||||
core::event::wayland::Event::RequestResize,
|
core::event::wayland::Event::RequestResize,
|
||||||
|
|
@ -1429,8 +1427,7 @@ async fn run_instance<P>(
|
||||||
&mut clipboard,
|
&mut clipboard,
|
||||||
&mut messages,
|
&mut messages,
|
||||||
);
|
);
|
||||||
let mut needs_redraw =
|
let mut needs_redraw = !messages.is_empty();
|
||||||
!no_window_events || !messages.is_empty();
|
|
||||||
if let Some(requested_size) =
|
if let Some(requested_size) =
|
||||||
clipboard.requested_logical_size.lock().unwrap().take()
|
clipboard.requested_logical_size.lock().unwrap().take()
|
||||||
{
|
{
|
||||||
|
|
@ -1486,8 +1483,17 @@ async fn run_instance<P>(
|
||||||
mouse_interaction,
|
mouse_interaction,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
let mouse_changed =
|
||||||
|
window.mouse_interaction != mouse_interaction;
|
||||||
|
|
||||||
window.update_mouse(mouse_interaction);
|
window.update_mouse(mouse_interaction);
|
||||||
|
|
||||||
|
if mouse_changed {
|
||||||
|
window.request_redraw(
|
||||||
|
core::window::RedrawRequest::NextFrame,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unconditional-rendering"))]
|
#[cfg(not(feature = "unconditional-rendering"))]
|
||||||
window.request_redraw(_redraw_request);
|
window.request_redraw(_redraw_request);
|
||||||
}
|
}
|
||||||
|
|
@ -1547,22 +1553,7 @@ async fn run_instance<P>(
|
||||||
));
|
));
|
||||||
|
|
||||||
for action in actions {
|
for action in actions {
|
||||||
let exited = run_action(
|
let exited = run_action!(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,
|
|
||||||
);
|
|
||||||
if exited {
|
if exited {
|
||||||
runtime.track(None.into_iter());
|
runtime.track(None.into_iter());
|
||||||
}
|
}
|
||||||
|
|
@ -1772,7 +1763,6 @@ async fn run_instance<P>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// search windows for widget with operation
|
// search windows for widget with operation
|
||||||
|
|
||||||
if result.is_none() {
|
if result.is_none() {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"start_dnd: widget {:?} not found; drag will fail",
|
"start_dnd: widget {:?} not found; drag will fail",
|
||||||
|
|
@ -2106,6 +2096,11 @@ fn run_action<'a, P, C>(
|
||||||
is_window_opening: &mut bool,
|
is_window_opening: &mut bool,
|
||||||
system_theme: &mut theme::Mode,
|
system_theme: &mut theme::Mode,
|
||||||
platform_specific: &mut crate::platform_specific::PlatformSpecific,
|
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
|
) -> bool
|
||||||
where
|
where
|
||||||
P: Program,
|
P: Program,
|
||||||
|
|
@ -2159,7 +2154,7 @@ where
|
||||||
_ = control_sender.start_send(Control::Cleanup(id)).ok();
|
_ = control_sender.start_send(Control::Cleanup(id)).ok();
|
||||||
}
|
}
|
||||||
let proxy = clipboard.proxy();
|
let proxy = clipboard.proxy();
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
platform_specific
|
platform_specific
|
||||||
.send_wayland(platform_specific::Action::RemoveWindow(id));
|
.send_wayland(platform_specific::Action::RemoveWindow(id));
|
||||||
if let Some(window) = window_manager.remove(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;
|
*compositor = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2543,6 +2538,8 @@ where
|
||||||
while let Some(mut operation) = current_operation.take() {
|
while let Some(mut operation) = current_operation.take() {
|
||||||
for (id, ui) in interfaces.iter_mut() {
|
for (id, ui) in interfaces.iter_mut() {
|
||||||
if let Some(window) = window_manager.get_mut(*id) {
|
if let Some(window) = window_manager.get_mut(*id) {
|
||||||
|
operation.as_mut().set_window_id(*id);
|
||||||
|
|
||||||
ui.operate(&window.renderer, operation.as_mut());
|
ui.operate(&window.renderer, operation.as_mut());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2620,6 +2617,10 @@ where
|
||||||
}
|
}
|
||||||
iced_runtime::dnd::DndAction::EndDnd => {
|
iced_runtime::dnd::DndAction::EndDnd => {
|
||||||
clipboard.end_dnd();
|
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) => {
|
iced_runtime::dnd::DndAction::PeekDnd(m, channel) => {
|
||||||
let data = clipboard.peek_dnd(m);
|
let data = clipboard.peek_dnd(m);
|
||||||
|
|
@ -2657,10 +2658,10 @@ where
|
||||||
.and_then(|window| theme::Base::palette(window.state.theme()))
|
.and_then(|window| theme::Base::palette(window.state.theme()))
|
||||||
});
|
});
|
||||||
|
|
||||||
cached_user_interfaces
|
window_manager
|
||||||
.drain()
|
.iter_mut()
|
||||||
.filter_map(|(id, cache)| {
|
.filter_map(|(id, window)| {
|
||||||
let window = window_manager.get_mut(id)?;
|
let cache = cached_user_interfaces.remove(&id)?;
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
id,
|
id,
|
||||||
|
|
@ -2733,7 +2734,5 @@ fn system_information(
|
||||||
graphics_backend: graphics.backend,
|
graphics_backend: graphics.backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "program")]
|
|
||||||
pub use program::Program;
|
|
||||||
|
|
||||||
pub use platform_specific::*;
|
pub use platform_specific::*;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,20 @@
|
||||||
//! Wayland specific shell
|
//! Wayland specific shell
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
use iced_graphics::compositor;
|
||||||
use cctk::sctk::reexports::client::Connection;
|
|
||||||
use iced_graphics::{Compositor, compositor};
|
|
||||||
use iced_runtime::{
|
use iced_runtime::{
|
||||||
core::{Vector, window},
|
core::{Vector, window},
|
||||||
platform_specific, user_interface,
|
user_interface,
|
||||||
};
|
};
|
||||||
use winit::raw_window_handle::HasWindowHandle;
|
use winit::raw_window_handle::HasWindowHandle;
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
pub use wayland::*;
|
pub use wayland::*;
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
|
||||||
use wayland_backend::client::Backend;
|
|
||||||
|
|
||||||
use crate::{CreateCompositor, Program, WindowManager};
|
use crate::{CreateCompositor, Program, WindowManager};
|
||||||
|
|
||||||
|
|
@ -35,7 +31,7 @@ pub type UserInterfaces<'a, P> = HashMap<
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
Wayland(sctk_event::SctkEvent),
|
Wayland(sctk_event::SctkEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +57,7 @@ impl SurfaceIdWrapper {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PlatformSpecific {
|
pub struct PlatformSpecific {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
wayland: WaylandSpecific,
|
wayland: WaylandSpecific,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,25 +67,30 @@ impl PlatformSpecific {
|
||||||
action: iced_runtime::platform_specific::Action,
|
action: iced_runtime::platform_specific::Action,
|
||||||
) {
|
) {
|
||||||
match action {
|
match action {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
iced_runtime::platform_specific::Action::Wayland(a) => {
|
iced_runtime::platform_specific::Action::Wayland(a) => {
|
||||||
self.send_wayland(wayland::Action::Action(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>(
|
pub(crate) fn retain_subsurfaces<F: Fn(window::Id) -> bool>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keep: F,
|
keep: F,
|
||||||
) {
|
) {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
self.wayland.retain_subsurfaces(keep);
|
self.wayland.retain_subsurfaces(keep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear_subsurface_list(&mut self) {
|
pub(crate) fn clear_subsurface_list(&mut self) {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
self.wayland.clear_subsurface_list();
|
self.wayland.clear_subsurface_list();
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +101,7 @@ impl PlatformSpecific {
|
||||||
id: window::Id,
|
id: window::Id,
|
||||||
window: &dyn HasWindowHandle,
|
window: &dyn HasWindowHandle,
|
||||||
) {
|
) {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
use cctk::sctk::reexports::client::{
|
use cctk::sctk::reexports::client::{
|
||||||
Proxy, protocol::wl_surface::WlSurface,
|
Proxy, protocol::wl_surface::WlSurface,
|
||||||
|
|
@ -151,12 +152,15 @@ impl PlatformSpecific {
|
||||||
pub(crate) fn create_surface(
|
pub(crate) fn create_surface(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Option<Box<dyn HasWindowHandle + Send + Sync + 'static>> {
|
) -> 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(
|
pub(crate) fn update_surface_shm(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -167,7 +171,7 @@ impl PlatformSpecific {
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
offset: Vector,
|
offset: Vector,
|
||||||
) {
|
) {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
{
|
{
|
||||||
return self.wayland.update_surface_shm(
|
return self.wayland.update_surface_shm(
|
||||||
surface, width, height, scale, data, offset,
|
surface, width, height, scale, data, offset,
|
||||||
|
|
@ -195,7 +199,7 @@ pub(crate) async fn handle_event<'a, 'b, P>(
|
||||||
P: Program,
|
P: Program,
|
||||||
{
|
{
|
||||||
match e {
|
match e {
|
||||||
#[cfg(all(feature = "cctk", target_os = "linux"))]
|
#[cfg(wayland_platform)]
|
||||||
Event::Wayland(e) => {
|
Event::Wayland(e) => {
|
||||||
platform_specific
|
platform_specific
|
||||||
.wayland
|
.wayland
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::core::window::Id as SurfaceId;
|
use crate::core::window::Id as SurfaceId;
|
||||||
use iced_runtime::{
|
use iced_runtime::{
|
||||||
self,
|
self, Action, Task,
|
||||||
platform_specific::{self, wayland},
|
platform_specific::{self, wayland},
|
||||||
task, Action, Task,
|
task,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn request_token(
|
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_futures::core::window;
|
||||||
use iced_runtime::{
|
use iced_runtime::{
|
||||||
self,
|
self, Action, Task,
|
||||||
platform_specific::{
|
platform_specific::{
|
||||||
self,
|
self,
|
||||||
wayland::{self, CornerRadius},
|
wayland::{self, CornerRadius},
|
||||||
},
|
},
|
||||||
task, Action, Task,
|
task,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn corner_radius(
|
pub fn corner_radius(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced_runtime::{
|
use iced_runtime::{
|
||||||
self,
|
self, Action, Task,
|
||||||
platform_specific::{self, wayland},
|
platform_specific::{self, wayland},
|
||||||
task, Action, Task,
|
task,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn inhibit_shortcuts(inhibit: bool) -> Task<()> {
|
pub fn inhibit_shortcuts(inhibit: bool) -> Task<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
//! Interact with the window of your application.
|
//! Interact with the window of your application.
|
||||||
|
|
||||||
|
use crate::core::Rectangle;
|
||||||
use crate::core::window::Id as SurfaceId;
|
use crate::core::window::Id as SurfaceId;
|
||||||
use iced_runtime::{
|
use iced_runtime::{
|
||||||
self,
|
self, Action, Task,
|
||||||
platform_specific::{
|
platform_specific::{
|
||||||
self,
|
self,
|
||||||
wayland::{
|
wayland::{
|
||||||
|
|
@ -10,7 +11,7 @@ use iced_runtime::{
|
||||||
layer_surface::{IcedMargin, SctkLayerSurfaceSettings},
|
layer_surface::{IcedMargin, SctkLayerSurfaceSettings},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
task, Action, Task,
|
task,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
|
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.
|
//! Interact with the wayland objects of your application.
|
||||||
|
|
||||||
pub mod activation;
|
pub mod activation;
|
||||||
|
pub mod blur;
|
||||||
pub mod corner_radius;
|
pub mod corner_radius;
|
||||||
pub mod keyboard_shortcuts_inhibit;
|
pub mod keyboard_shortcuts_inhibit;
|
||||||
pub mod layer_surface;
|
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