Compare commits

...

57 commits

Author SHA1 Message Date
1a57634add yoda: cargo fix sweep post-rebase (unused imports/vars)
Some checks failed
Check / widget (push) Has been cancelled
Audit / vulnerabilities (push) Has been cancelled
Check / wasm (push) Has been cancelled
Document / all (push) Has been cancelled
Format / all (push) Has been cancelled
Lint / all (push) Has been cancelled
Test / all (macOS-latest, 1.88) (push) Has been cancelled
Test / all (macOS-latest, beta) (push) Has been cancelled
Test / all (macOS-latest, stable) (push) Has been cancelled
Test / all (ubuntu-latest, 1.88) (push) Has been cancelled
Test / all (ubuntu-latest, beta) (push) Has been cancelled
Test / all (ubuntu-latest, stable) (push) Has been cancelled
Test / all (windows-latest, 1.88) (push) Has been cancelled
Test / all (windows-latest, beta) (push) Has been cancelled
Test / all (windows-latest, stable) (push) Has been cancelled
Leyoda 2026 – GPLv3
2026-07-02 19:10:42 +02:00
362b4abb15 chore: use local winit and softbuffer 2026-07-02 18:44:56 +02:00
cf7828fb70 chore: use local glyphon and cosmic-text 2026-07-02 18:44:56 +02:00
3982e04aa2 chore: use local cctk and clipboard crates 2026-07-02 18:44:11 +02:00
4e2e58e03d chore: gate accessibility-only winit state 2026-07-02 18:43:40 +02:00
Votre Nom
b90b1413a6 yoda: point window_clipboard at public Forgejo fork
Replace pop-os/window_clipboard sctk-0.20 (no wayland feature) with
forge.aditua.com/leyoda/window_clipboard branch yoda-x11-optional. This
removes the need for cosmic-files' [patch] redirect to a local path and
makes libcosmic build standalone again.

Leyoda 2026 – GPLv3
2026-07-02 18:42:57 +02:00
Votre Nom
ce5ea665e2 Fix high-signal widget and Wayland warnings
Avoid recursive operation traversal, execute visible list updates instead of leaving a lazy iterator unused, remove unreachable cfg-gated surface creation code, and handle subsurface buffer attachment failures.
2026-07-02 18:42:57 +02:00
f1f42f2bff yoda: softbuffer + window_clipboard default-features=false
Two one-line workspace-dep fixes that drop the last remaining X11 crates
for Wayland-only builds:

- softbuffer: its default = [kms, x11, x11-dlopen, wayland, wayland-dlopen]
  pulled tiny-xlib + as-raw-xcb-connection unconditionally even though
  iced_tiny_skia only needed softbuffer/wayland. Disabling the default
  lets iced_tiny_skia's own feature propagation ask for just wayland +
  wayland-dlopen.

- window_clipboard: upstream pulled clipboard_x11 + clipboard_wayland
  unconditionally on unix. Our local fork (branch yoda-x11-optional)
  gates clipboard_x11 behind an opt-in feature, so default-features=false
  + features=["wayland"] drops ~500 x11 symbols (clipboard_x11, x11rb,
  x11rb-protocol).

Combined with previous yoda-v4 cuts, cosmic-yoterm goes from
1526 -> 43 x11 symbols (-97%), and 55.4 MB -> 53.3 MB (-2.1 MB).
2026-07-02 18:42:42 +02:00
702b6359a5 yoda: gate iced_wgpu x11 backend behind an opt-in feature
iced_wgpu's unix block unconditionally depended on as-raw-xcb-connection,
tiny-xlib and x11rb for a DRI3 device-id fallback in
window/compositor.rs that only fires when wayland can't determine the
GPU. On a Wayland-only build this code path never runs, so the deps
are pure bloat.

Changes:
- Cargo.toml: move the 3 x11 crates to optional + new feature `x11`
  (not in default — wayland-only is the yoda target; users who need
  X11 opt in).
- window.rs: cfg-gate `mod x11;` on feature = "x11"
- window/compositor.rs: cfg-gate `use super::x11::get_x11_device_ids`
  + the or_else fallback on feature = "x11" (wrapped in a let-binding
  so the non-x11 branch compiles cleanly).

Result: iced_wgpu no longer pulls any x11 crate when built without
the feature.
2026-07-02 18:42:13 +02:00
a50a29b390 yoda: drop x11 default on winit workspace dep
Was pulling winit's full x11 backend (winit-x11 + x11-dl) unconditionally
despite iced_winit's own x11 feature being disabled. Fixed by adding
default-features=false on the workspace-level winit dep — iced_winit/wayland
+ wayland-dlopen propagate to winit/wayland + wayland-dlopen as needed.
2026-07-02 18:42:13 +02:00
Ashley Wulber
c781ff6199 chore: cargo fmt 2026-07-01 10:37:52 -04:00
Ashley Wulber
633ed14411 feat: popup reposition 2026-07-01 10:37:52 -04:00
Ashley Wulber
da9d15b2e8 chore: update cosmic-protocols 2026-07-01 10:37:52 -04:00
Ashley Wulber
8e94441efa fix: apply initial corner radius after adding surfaces to the state
otherwise the corners won't be set.
2026-07-01 10:37:52 -04:00
Ashley Wulber
46781102fe fix: set blur and corners on creation 2026-07-01 10:37:52 -04:00
Ashley Wulber
5cc9f8d621 fix: single blur surface per wl surface 2026-07-01 10:37:52 -04:00
Ashley Wulber
6be49cb530 blur command helper 2026-07-01 10:37:52 -04:00
Ashley Wulber
b8b0804140 fix(overlap): fix check for disabling 2026-07-01 10:37:52 -04:00
Ashley Wulber
6521f7fa20 fix: improved layer surface popup cleanup 2026-07-01 10:37:52 -04:00
Ashley Wulber
f6fa506f6c chore(layer shell): add padding action 2026-07-01 10:37:52 -04:00
Ashley Wulber
e0c7d353aa better handle pending corners 2026-07-01 10:37:52 -04:00
Ashley Wulber
a63d57261f blur enabled event 2026-07-01 10:37:52 -04:00
Ashley Wulber
1bcba7e30c wip: corner radius v2 2026-07-01 10:37:52 -04:00
Hojjat
9f41055861 Revert "feat: add direct SCTK action sender for wayland popups"
This reverts commit b007caba44.
2026-06-30 12:26:07 -04:00
Hojjat
7afd9ee0f8 fix: add alt clipboard bindings for text_editor 2026-06-21 06:49:01 -04:00
Jeremy Soller
62b90179a2 Fix List widget not updating children because iterator not consumed 2026-06-10 12:04:26 -06:00
Ilia Malanin
c3c8fcf52c refactor: wrap repeated run_action call in a macro 2026-06-09 10:14:25 -04:00
Ilia Malanin
f725961c87 fix: reuse wgpu compositor across windows 2026-06-09 10:14:25 -04:00
Ashley Wulber
dcf4cf0194 improv: ignore duplicate surfaces and log a warning 2026-06-06 02:22:09 +02:00
Hojjat
5013f63956 improv: reuse loaded fonts by cosmic-text for svg renderer 2026-05-30 02:56:54 +02:00
Ashley Wulber
ebb3b125bf Revert "fix(text): don't reload cache every redraw"
This reverts commit 590e093086.
2026-05-29 16:43:44 -04:00
Gavin John
590e093086 fix(text): don't reload cache every redraw
I have >1000 fonts installed. COSMIC has always been somewhat laggy for me, but I had assumed it was just like that for everyone, or was the fault of my T480s. Eventually, when trying to diagnose some hitching, I ended up discovering that cosmic-launcher was consuming on average ~5% total CPU over time, which led me to discover that it was fetching all the fonts from disk and software rendering the fonts, every redraw, causing the high cpu usage *and* the stutters (as a result of hammering disk I/O). Oops.

The fix is simple: just persist the cache across redraws.
2026-05-29 19:56:54 +02:00
Ashley Wulber
9c748f1656 Revert "fix: avoid deadlock in context menu rendering"
This reverts commit 0094b347d4.
2026-05-27 17:25:49 -04:00
Hojjat
dfcd989947 fix: present suboptimal frames 2026-05-27 17:22:00 -04:00
Hojjat
0094b347d4 fix: avoid deadlock in context menu rendering 2026-05-27 17:22:00 -04:00
Ashley Wulber
a7d71d9ace chore: modifiers event on keyboard enter and leave 2026-05-27 20:01:47 +02:00
Ashley Wulber
6105d8bdef reexport geometry from iced_graphics 2026-05-22 15:42:09 -04:00
Michael Aaron Murphy
98fb6a0fba
docs: update cosmic-protocols with docs cfg fix 2026-05-20 17:00:28 +02:00
Hojjat
b007caba44 feat: add direct SCTK action sender for wayland popups 2026-05-20 16:04:35 +02:00
Hojjat
a7ca336eb4 feat: HasSelectableText impl to TextEditor 2026-05-20 16:04:35 +02:00
Hojjat
2e1e4bd6e0 feat: add HasSelectableText trait to Text widget 2026-05-20 16:04:35 +02:00
Hojjat
9ae155b8e2 fix: crash while resizing on x11 2026-05-20 16:00:15 +02:00
Ericky Dos Santos
346faf2e79 Make shader_f16 optional because it does not work in webgl 2026-05-20 07:26:41 -04:00
Hojjat
251254ad51 feat: add selectable() to Text 2026-05-16 08:28:37 -04:00
Ashley Wulber
347d91f7ea improv(wgpu): adapter selection 2026-05-10 22:41:11 -04:00
KENZ
1444d5c43e fix(ime): Don't panic if some IME sends DeleteSurrounding events 2026-05-08 00:23:00 +02:00
KENZ
f752bba18f Revert "Fix the candidate window position of fcitx5 which does't work well wi…"
This reverts commit a9bcb00529.
2026-05-08 00:22:22 +02:00
Lcstyle
d7595fab95 fix: bootstrap FrameStatus on session lock surface configure
The session lock configure handler was missing receive_frame() and
request_redraw() calls that the layer surface configure handler has.
Without these, after S3 resume or DPMS wake, the FrameStatus entry
for lock surfaces could be missing, causing the rendering gate to
never reach Ready state — resulting in permanently grey lock screens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 12:40:09 -04:00
Ashley Wulber
e000c71ff2 fix: send redraw request after events
creation events must be handled before processing redraws
2026-05-07 17:39:02 +02:00
edwloef
3928e0d385 wgpu: request devices with SHADER_F16 feature 2026-04-27 15:03:07 +02:00
Hojjat
90d2c418a7 chore: update to wgpu 28 and cosmic-text 0.19 2026-04-27 14:35:24 +02:00
KENZ
aa2a870c35 fix: Forward wayland IME events in SCTK only when single-instance
Some checks failed
Audit / vulnerabilities (push) Has been cancelled
Test / all (ubuntu-latest, 1.88) (push) Has been cancelled
Build / todos_linux (push) Has been cancelled
Build / todos_windows (push) Has been cancelled
Build / todos_macos (push) Has been cancelled
Build / todos_raspberry (push) Has been cancelled
Check / wasm (push) Has been cancelled
Check / widget (push) Has been cancelled
Test / all (ubuntu-latest, beta) (push) Has been cancelled
Document / all (push) Has been cancelled
Format / all (push) Has been cancelled
Lint / all (push) Has been cancelled
Test / all (macOS-latest, 1.88) (push) Has been cancelled
Test / all (macOS-latest, beta) (push) Has been cancelled
Test / all (macOS-latest, stable) (push) Has been cancelled
Test / all (ubuntu-latest, stable) (push) Has been cancelled
Test / all (windows-latest, 1.88) (push) Has been cancelled
Test / all (windows-latest, beta) (push) Has been cancelled
Test / all (windows-latest, stable) (push) Has been cancelled
feature enabled
2026-04-18 16:12:59 -04:00
Ian Douglas Scott
8f10a3bd34 winit: Set WindowAttributesWayland without cctk feature
There doesn't seem to be any reason for this to require the `cctk`
feature?

Fixes the app ID, and therefore icon, for `cosmic-sync-gui`.
2026-04-16 12:59:35 -06:00
boloto1979
64b97077b7 fix(wayland): add mouse cursor entered event to SctkEvent 2026-04-16 12:27:46 -04:00
Ashley Wulber
13b8d3eab6 fix: just use 0 for position of blur rectangle 2026-04-14 23:21:27 -04:00
Ashley Wulber
1bf1e33317 improv(wayland): handle capabilities for ext_background_effect 2026-04-14 18:21:36 -04:00
Ashley Wulber
d2b2715835 feat: ext-background-effect blur 2026-04-14 18:21:36 -04:00
90 changed files with 2236 additions and 990 deletions

772
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -120,7 +120,7 @@ winit = [
]
wayland = ["iced_renderer/wayland", "iced_winit/wayland"]
cctk = ["iced_winit/cctk", "iced_widget/cctk", "iced_core/cctk", "wayland"]
# Enables the sctk shell.
# Enables the sctk shell.
[dependencies]
@ -202,7 +202,7 @@ repository = "https://github.com/iced-rs/iced"
homepage = "https://iced.rs"
categories = ["gui"]
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
rust-version = "1.89"
rust-version = "1.92"
[workspace.dependencies]
iced = { version = "0.14.0", path = "." }
@ -238,10 +238,9 @@ async-std = "1.0"
bitflags = "2.5"
bytemuck = { version = "1.0", features = ["derive"] }
bytes = "1.6"
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git" }
# cosmic-text = "0.10"
cosmic-text = { path = "../../cosmic-text" }
cryoglyph = { package = "cryoglyph", path = "../../glyphon" }
dark-light = "1.0"
cryoglyph = { package = "cryoglyph", git = "https://github.com/pop-os/glyphon.git", tag = "cosmic-0.14" }
resvg = "0.45"
web-sys = "0.3.69"
guillotiere = "0.6"
@ -275,8 +274,11 @@ tiny-skia = { version = "0.11", default-features = false, features = [
"std",
"simd",
] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086" }
softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" }
cctk = { path = "../../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit" }
# Yoda: default-features=false drops softbuffer's x11 / x11-dlopen (pulled
# tiny-xlib + as-raw-xcb-connection). The wayland + wayland-dlopen features
# are re-activated via iced_tiny_skia's own `wayland` feature propagation.
softbuffer = { path = "../../softbuffer", default-features = false }
syntect = "5.2"
tokio = "1.0"
tracing = "0.1"
@ -288,7 +290,7 @@ url = "2.5"
wasm-bindgen-futures = "0.4"
wasmtimer = "0.4.2"
web-time = "1.1"
wgpu = { version = "27.0", default-features = false, features = [
wgpu = { version = "28.0", default-features = false, features = [
"std",
"wgsl",
] }
@ -302,13 +304,40 @@ wayland-client = { version = "0.31.5" }
winapi = "0.3"
# window_clipboard = "0.4.1"
window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "sctk-0.20" }
winit = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
winit-core = { git = "https://github.com/pop-os/winit.git", tag = "cosmic-0.14" }
# Yoda: window_clipboard pinned to our public fork that has x11 gated behind
# an opt-in feature. default-features=false + features=["wayland"] drops
# clipboard_x11 + x11rb from the build. Branch yoda-x11-optional on
# https://forge.aditua.com/leyoda/window_clipboard.
window_clipboard = { path = "../../window_clipboard", default-features = false, features = ["wayland"] }
dnd = { path = "../../window_clipboard/dnd" }
mime = { path = "../../window_clipboard/mime" }
# Yoda: default-features = false drops winit's x11 default, which otherwise
# pulls winit-x11, x11-dl, tiny-xlib, as-raw-xcb-connection. Our Wayland-only
# fork doesn't need any of that — the wayland + wayland-dlopen features are
# re-enabled via iced_winit's own feature propagation from libcosmic-yoda.
winit = { path = "../../winit/winit", default-features = false }
winit-core = { path = "../../winit/winit-core" }
cursor-icon = "1.1.0"
[patch.'https://github.com/pop-os/softbuffer']
softbuffer = { path = "../../softbuffer" }
[patch.'https://github.com/pop-os/smithay-clipboard']
smithay-clipboard = { path = "../../smithay-clipboard" }
[patch.'https://github.com/pop-os/winit.git']
dpi = { path = "../../winit/dpi" }
winit = { path = "../../winit/winit" }
winit-android = { path = "../../winit/winit-android" }
winit-appkit = { path = "../../winit/winit-appkit" }
winit-common = { path = "../../winit/winit-common" }
winit-core = { path = "../../winit/winit-core" }
winit-orbital = { path = "../../winit/winit-orbital" }
winit-uikit = { path = "../../winit/winit-uikit" }
winit-wayland = { path = "../../winit/winit-wayland" }
winit-web = { path = "../../winit/winit-web" }
winit-win32 = { path = "../../winit/winit-win32" }
winit-x11 = { path = "../../winit/winit-x11" }
[workspace.lints.rust]
rust_2018_idioms = { level = "deny", priority = -1 }

View file

@ -31,6 +31,7 @@ num-traits.workspace = true
rustc-hash.workspace = true
smol_str.workspace = true
thiserror.workspace = true
unicode-segmentation.workspace = true
web-time.workspace = true
serde.workspace = true

View file

@ -1,4 +1,4 @@
use crate::event::{self, Event};
use crate::event::Event;
use crate::id::Id;
use crate::layout;
use crate::mouse;

View file

@ -46,4 +46,6 @@ pub enum Event {
Subsurface(SubsurfaceEvent),
/// Keyboard inhibit shortcuts
ShortcutsInhibited(bool),
/// Blur Support Enabled
BlurEnabled,
}

View file

@ -3,7 +3,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
use crate::{Clipboard, Event, Layout, Overlay, Shell, Size};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children.

View file

@ -78,6 +78,8 @@ pub enum Action {
SelectLine,
/// Select the entire buffer.
SelectAll,
/// Clear the selection
ClearSelection,
/// Perform an [`Edit`].
Edit(Edit),
/// Click the [`Editor`] at the given [`Point`].

View file

@ -4,7 +4,7 @@ use super::Operation;
use crate::{
Rectangle,
id::Id,
widget::operation::{Outcome, focusable::Count},
widget::operation::Outcome,
};
/// Produces an [`Operation`] that searches for the Id

View file

@ -22,16 +22,18 @@
//! ```
use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::mouse::{self, click};
use crate::renderer;
use crate::text;
use crate::text::paragraph::{self, Paragraph};
use crate::text::{self, Fragment};
use crate::widget::tree::{self, Tree};
use crate::{
Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget,
Clipboard, Color, Element, Event, Layout, Length, Pixels, Point, Rectangle,
Shell, Size, Theme, Widget, keyboard, touch,
};
use std::borrow::Cow;
use unicode_segmentation::UnicodeSegmentation;
pub use text::{Alignment, Ellipsize, LineHeight, Shaping, Wrapping};
/// A bunch of text.
@ -65,6 +67,7 @@ where
fragment: text::Fragment<'a>,
format: Format<Renderer::Font>,
class: Theme::Class<'a>,
selectable: bool,
}
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
@ -79,6 +82,7 @@ where
fragment: fragment.into_fragment(),
format: Format::default(),
class: Theme::default(),
selectable: false,
}
}
@ -189,7 +193,17 @@ where
{
let color = color.map(Into::into);
self.style(move |_theme| Style { color })
self.style(move |_theme| Style {
color,
..Style::default()
})
}
/// Makes the [`Text`] selectable. When enabled, the user can click and
/// drag to select text, and copy it with Ctrl+C / Cmd+C.
pub fn selectable(mut self) -> Self {
self.selectable = true;
self
}
/// Sets the style class of the [`Text`].
@ -202,7 +216,114 @@ where
}
/// The internal state of a [`Text`] widget.
pub type State<P> = paragraph::Plain<P>;
pub struct State<P: Paragraph> {
/// The cached paragraph layout.
pub paragraph: paragraph::Plain<P>,
/// Lazily allocated when text is selectable and first interacted with.
selection: Option<Box<SelectionState>>,
focused: bool,
keyboard_focused: bool,
context_menu_position: Option<Point>,
}
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,
}
}
}
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) -> &paragraph::Plain<P> {
&self.paragraph
}
}
impl<P: Paragraph> std::ops::DerefMut for State<P> {
fn deref_mut(&mut self) -> &mut paragraph::Plain<P> {
&mut self.paragraph
}
}
#[derive(Debug, Clone, Default)]
struct SelectionState {
anchor: usize,
end: usize,
dragging: bool,
modifiers: keyboard::Modifiers,
last_click: Option<click::Click>,
}
impl SelectionState {
fn clear(&mut self) {
self.anchor = 0;
self.end = 0;
self.dragging = false;
}
}
impl<P: Paragraph> crate::widget::operation::Focusable for State<P> {
fn is_focused(&self) -> bool {
self.focused
}
fn focus(&mut self) {
self.focused = true;
self.keyboard_focused = true;
}
fn unfocus(&mut self) {
self.clear_focus();
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'_, Theme, Renderer>
@ -215,7 +336,7 @@ where
}
fn state(&self) -> tree::State {
tree::State::new(paragraph::Plain::<Renderer::Paragraph>::default())
tree::State::new(State::<Renderer::Paragraph>::default())
}
fn size(&self) -> Size<Length> {
@ -231,8 +352,10 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
layout(
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
&mut state.paragraph,
renderer,
limits,
&self.fragment,
@ -252,25 +375,325 @@ where
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let style = theme.style(&self.class);
let bounds = layout.bounds();
let paragraph = state.paragraph.raw();
if let Some(sel) = &state.selection {
let left = sel.anchor.min(sel.end);
let right = sel.anchor.max(sel.end);
let content: &str = self.fragment.as_ref();
draw(
renderer,
defaults,
layout.bounds(),
state.raw(),
style,
viewport,
);
if left != right {
let lo_byte = grapheme_to_byte(content, left);
let hi_byte = grapheme_to_byte(content, right);
let anchor = bounds.anchor(
paragraph.min_bounds(),
paragraph.align_x(),
paragraph.align_y(),
);
let rects = paragraph.highlight(
0,
(lo_byte, text::Affinity::After),
(hi_byte, text::Affinity::Before),
);
for r in rects {
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: anchor.x + r.x,
y: anchor.y + r.y,
width: r.width,
height: r.height,
},
..renderer::Quad::default()
},
style.selected_fill,
);
}
}
}
draw(renderer, defaults, bounds, paragraph, style, 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() {
state.focused = false;
state.keyboard_focused = false;
if let Some(sel) = &mut state.selection {
sel.clear();
}
}
}
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();
}
}
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
if let Some(pos) = cursor.position_over(bounds) {
state.context_menu_position = Some(pos);
state.focused = true;
state.keyboard_focused = false;
shell.capture_event();
}
}
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,
);
sel.end = hit_to_grapheme(paragraph, relative, content);
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();
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();
}
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();
}
keyboard::Key::Named(keyboard::key::Named::Home) => {
if modifiers.shift() {
sel.end = 0;
} else {
sel.anchor = 0;
sel.end = 0;
}
shell.capture_event();
}
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();
}
_ => {}
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
if let Some(sel) = &mut state.selection {
sel.modifiers = *modifiers;
}
}
_ => {}
}
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if self.selectable && cursor.is_over(layout.bounds()) {
mouse::Interaction::Text
} else {
mouse::Interaction::None
}
}
fn operate(
&mut self,
_tree: &mut Tree,
tree: &mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
operation: &mut dyn super::Operation,
) {
operation.text(None, layout.bounds(), &self.fragment);
if self.selectable {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
operation.focusable(None, layout.bounds(), state);
}
}
#[cfg(feature = "a11y")]
@ -471,12 +894,23 @@ where
}
/// The appearance of some text.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
/// The fill [`Color`] of the selection highlight.
pub selected_fill: Color,
}
impl Default for Style {
fn default() -> Self {
Self {
color: None,
selected_fill: DEFAULT_SELECTION_COLOR,
}
}
}
/// The theme catalog of a [`Text`].
@ -510,13 +944,14 @@ impl Catalog for Theme {
/// The default text styling; color is inherited.
pub fn default(_theme: &Theme) -> Style {
Style { color: None }
Style::default()
}
/// Text with the default base color.
pub fn base(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().text),
..Style::default()
}
}
@ -524,6 +959,7 @@ pub fn base(theme: &Theme) -> Style {
pub fn primary(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().primary),
..Style::default()
}
}
@ -531,6 +967,7 @@ pub fn primary(theme: &Theme) -> Style {
pub fn secondary(theme: &Theme) -> Style {
Style {
color: Some(theme.extended_palette().secondary.base.color),
..Style::default()
}
}
@ -538,6 +975,7 @@ pub fn secondary(theme: &Theme) -> Style {
pub fn success(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().success),
..Style::default()
}
}
@ -545,6 +983,7 @@ pub fn success(theme: &Theme) -> Style {
pub fn warning(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().warning),
..Style::default()
}
}
@ -552,5 +991,177 @@ pub fn warning(theme: &Theme) -> Style {
pub fn danger(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().danger),
..Style::default()
}
}
use crate::widget::tree::Tree as WidgetTree;
/// 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 (enables Cut / Paste).
fn is_editable(&self) -> bool {
false
}
/// 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 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()
}
}

View file

@ -1,5 +1,5 @@
use iced::widget::{button, column, text, Column};
use iced::Center;
use iced::widget::{Column, button, column, text};
pub fn main() -> iced::Result {
iced::run(Counter::update, Counter::view)
@ -42,7 +42,7 @@ impl Counter {
#[cfg(test)]
mod tests {
use super::*;
use iced_test::{Error, simulator};
use iced_test::{simulator, Error};
#[test]
fn it_counts() -> Result<(), Error> {

View file

@ -140,7 +140,7 @@ impl Pipeline {
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
@ -223,7 +223,7 @@ impl Pipeline {
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("cubes pipeline layout"),
bind_group_layouts: &[&uniform_bind_group_layout],
push_constant_ranges: &[],
immediate_size: 0,
});
let shader =
@ -280,7 +280,7 @@ impl Pipeline {
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
multiview_mask: None,
cache: None,
});
@ -388,6 +388,7 @@ impl Pipeline {
),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_viewport(
@ -478,7 +479,7 @@ impl DepthPipeline {
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("cubes.depth_pipeline.layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
immediate_size: 0,
});
let shader =
@ -520,7 +521,7 @@ impl DepthPipeline {
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
multiview_mask: None,
cache: None,
});
@ -586,6 +587,7 @@ impl DepthPipeline {
),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_viewport(

View file

@ -43,6 +43,7 @@ impl Scene {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
})
}
@ -64,8 +65,8 @@ fn build_pipeline(
let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
push_constant_ranges: &[],
bind_group_layouts: &[],
immediate_size: 0,
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@ -101,7 +102,7 @@ fn build_pipeline(
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
})
}

View file

@ -3,20 +3,20 @@ use std::{
sync::atomic::{AtomicU64, Ordering},
};
use iced::{Element, id::Id};
use iced::{
Event, Length, Rectangle,
clipboard::{
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
mime::AllowedMimeTypes,
},
event,
id::Internal,
mouse, overlay,
mouse, overlay, Event, Length, Rectangle,
};
use iced::{id::Id, Element};
use iced_core::{
self, Clipboard, Layout, Shell, Widget, layout,
widget::{Tree, tree},
self, layout,
widget::{tree, Tree},
Clipboard, Layout, Shell, Widget,
};
pub fn dnd_destination<'a, Message: 'static>(

View file

@ -1,16 +1,16 @@
use std::any::Any;
use iced::Element;
use iced::id::Id;
use iced::widget::container;
use iced::Element;
use iced::{
Event, Length, Point, Rectangle,
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay,
event, mouse, overlay, Event, Length, Point, Rectangle,
};
use iced_core::{
Clipboard, Shell, layout, renderer,
widget::{Tree, tree},
layout, renderer,
widget::{tree, Tree},
Clipboard, Shell,
};
use iced_core::{Layout, Widget};
@ -37,11 +37,11 @@ pub struct DndSource<'a, Message, AppMessage, D> {
}
impl<
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> DndSource<'a, Message, AppMessage, D>
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> DndSource<'a, Message, AppMessage, D>
{
pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
Self {
@ -119,11 +119,11 @@ impl<
}
impl<
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> Widget<Message, iced::Theme, iced::Renderer>
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> Widget<Message, iced::Theme, iced::Renderer>
for DndSource<'a, Message, AppMessage, D>
{
fn children(&self) -> Vec<Tree> {
@ -357,11 +357,11 @@ impl<
}
impl<
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
'a,
Message: 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
{
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
Element::new(e)

View file

@ -7,7 +7,6 @@ use cctk::sctk::reexports::{
use iced::platform_specific::shell::commands::subsurface::get_subsurface;
use iced::{
Element, Length, Subscription, Task,
event::wayland::Event as WaylandEvent,
platform_specific::{
runtime::wayland::subsurface::SctkSubsurfaceSettings,
@ -15,6 +14,7 @@ use iced::{
},
widget::{button, column, text, text_input},
window::{self, Id, Settings},
Element, Length, Subscription, Task,
};
use std::sync::{Arc, Mutex};

View file

@ -477,6 +477,7 @@ fn delete_icon() -> Text<'static> {
fn subtle(theme: &Theme) -> text::Style {
text::Style {
color: Some(theme.extended_palette().background.strongest.color),
..text::Style::default()
}
}

View file

@ -2,8 +2,6 @@
#[cfg(feature = "image")]
use crate::core::Bytes;
use crate::core::Color;
use crate::core::Radians;
use crate::core::Rectangle;
use crate::core::image;
use crate::core::svg;

View file

@ -161,6 +161,14 @@ impl FontSystem {
&mut self.raw
}
/// Returns a reference to the underlying [`fontdb::Database`].
///
/// Unlike going through [`raw`](Self::raw) and `db_mut`, this does not
/// clear `cosmic-text`'s internal font-match cache.
pub fn db(&self) -> &cosmic_text::fontdb::Database {
self.raw.db()
}
/// Loads a font from its bytes.
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
if let Cow::Borrowed(bytes) = bytes {

View file

@ -336,6 +336,10 @@ impl editor::Editor for Editor {
}
}
Action::ClearSelection => {
editor.set_selection(cosmic_text::Selection::None);
}
// Editing events
Action::Edit(edit) => {
let topmost_line_before_edit = editor

View file

@ -1,12 +1,10 @@
//! Access the clipboard.
use std::any::Any;
use dnd::{DndDestinationRectangle, DndSurface};
use iced_core::clipboard::DndSource;
use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use window_clipboard::mime::AllowedMimeTypes;
use crate::{oneshot, task, Action, Task};
use crate::{Action, Task, oneshot, task};
/// An action to be performed on the system.
pub enum DndAction {

View file

@ -28,7 +28,6 @@ pub use task::Task;
pub use user_interface::UserInterface;
pub use window::Window;
use crate::core::Color;
use crate::futures::futures::channel::oneshot;
use std::borrow::Cow;

View file

@ -36,7 +36,7 @@ impl fmt::Debug for Action {
f,
"Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}",
window, token,
)
),
}
}
}

View file

@ -6,8 +6,8 @@ use cctk::sctk::{
};
use iced_core::layout::Limits;
use iced_core::window::Id;
use iced_core::Rectangle;
use iced_core::window::Id;
/// output for layer surface
#[derive(Debug, Clone)]
@ -150,6 +150,13 @@ pub enum Action {
/// layer of the layer surface
layer: Layer,
},
/// padding of the surface geometry
Padding {
/// id of the layer surface
id: Id,
/// padding of the surface geometry
padding: IcedMargin,
},
}
impl fmt::Debug for Action {
@ -162,35 +169,51 @@ impl fmt::Debug for Action {
),
Action::Size { id, width, height } => write!(
f,
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height
"Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}",
id, width, height
),
Action::Destroy(id) => write!(
f,
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id
"Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}",
id
),
Action::Anchor { id, anchor } => write!(
f,
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor
"Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}",
id, anchor
),
Action::ExclusiveZone { id, exclusive_zone } => write!(
f,
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id
"Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}",
id
),
Action::Margin { id, margin } => write!(
f,
"Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", id, margin
"Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}",
id, margin
),
Action::KeyboardInteractivity { id, keyboard_interactivity } => write!(
Action::KeyboardInteractivity {
id,
keyboard_interactivity,
} => write!(
f,
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity
"Action::LayerSurfaceAction::KeyboardInteractivity {{ id: {:#?}, keyboard_interactivity: {:?} }}",
id, keyboard_interactivity
),
Action::InputZone { id, zone } => write!(
f,
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}", id, zone
"Action::LayerSurfaceAction::InputZone {{ id: {:#?}, zone: {:?} }}",
id, zone
),
Action::Layer { id, layer } => write!(
f,
"Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}", id, layer
"Action::LayerSurfaceAction::Layer {{ id: {:#?}, layer: {:?} }}",
id, layer
),
Action::Padding { id, padding } => write!(
f,
"Action::LayerSurfaceAction::Padding {{ id: {:#?}, padding: {:?} }}",
id, padding
),
}
}

View file

@ -2,7 +2,7 @@
use std::fmt::Debug;
use iced_core::window::Id;
use iced_core::{Rectangle, window::Id};
/// activation Actions
pub mod activation;
@ -35,6 +35,8 @@ pub enum Action {
InhibitShortcuts(bool),
/// Rounded corners in logical space
RoundedCorners(iced_core::window::Id, Option<CornerRadius>),
/// Blur effect for a surface
BlurSurface(Id, Option<Vec<Rectangle>>),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
@ -70,6 +72,11 @@ impl Debug for Action {
Action::RoundedCorners(id, v) => {
f.debug_tuple("RoundedCorners").field(id).field(v).finish()
}
Action::BlurSurface(id, rectangles) => f
.debug_tuple("BlurSurface")
.field(id)
.field(rectangles)
.finish(),
}
}
}

View file

@ -124,26 +124,34 @@ pub enum Action {
/// height
height: u32,
},
/// set the size of the popup
Reposition {
/// id of the popup
id: Id,
/// positioner of the popup
positioner: SctkPositioner,
},
}
impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Action::Popup { popup, .. } => write!(
f,
"Action::PopupAction::Popup {{ popup: {:?} }}",
popup
),
Action::Destroy { id } => write!(
f,
"Action::PopupAction::Destroy {{ id: {:?} }}",
id
),
Action::Popup { popup, .. } => {
write!(f, "Action::PopupAction::Popup {{ popup: {:?} }}", popup)
}
Action::Destroy { id } => {
write!(f, "Action::PopupAction::Destroy {{ id: {:?} }}", id)
}
Action::Size { id, width, height } => write!(
f,
"Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}",
id, width, height
),
Action::Reposition { id, positioner } => write!(
f,
"Action::Reposition {{ id: {:?}, positioner: {:?} }}",
id, positioner
),
}
}
}

View file

@ -683,6 +683,8 @@ pub use event::Event;
pub use executor::Executor;
pub use font::Font;
pub use renderer::Renderer;
#[cfg(feature = "canvas")]
pub use renderer::graphics::geometry;
pub use task::Task;
pub use window::Window;

View file

@ -775,6 +775,7 @@ impl<P: Program + 'static> Tester<P> {
}
_ => None,
},
..text::Style::default()
})
.into()
},
@ -945,6 +946,7 @@ where
text(label).size(14).style(|theme: &core::Theme| {
text::Style {
color: Some(theme.extended_palette().background.weak.text),
..text::Style::default()
}
}),
space::horizontal(),

View file

@ -1,5 +1,6 @@
use crate::core::svg::{Data, Handle};
use crate::core::{Color, Rectangle, Size};
use crate::graphics::text;
use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};
@ -74,6 +75,7 @@ struct Cache {
rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
raster_hits: FxHashSet<RasterKey>,
fontdb: Option<Arc<usvg::fontdb::Database>>,
fontdb_version: Option<text::Version>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -87,12 +89,15 @@ impl Cache {
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
let id = handle.id();
// TODO: Reuse `cosmic-text` font database
if self.fontdb.is_none() {
let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();
// Reuse the font database already loaded by the `cosmic-text` font
// system. We clone it once and refresh only when a font is
// loaded (i.e. when the font system's version changes).
let font_system = text::font_system().read().expect("Read font system");
let version = font_system.version();
self.fontdb = Some(Arc::new(fontdb));
if self.fontdb_version != Some(version) {
self.fontdb = Some(Arc::new(font_system.db().clone()));
self.fontdb_version = Some(version);
}
let options = usvg::Options {

View file

@ -25,6 +25,11 @@ svg = ["iced_graphics/svg", "resvg/text"]
web-colors = ["iced_graphics/web-colors"]
webgl = ["wgpu/webgl"]
strict-assertions = []
# Yoda: opt-in X11 backend. Pulls tiny-xlib/as-raw-xcb-connection/x11rb
# (which otherwise were unconditional direct deps on unix). Wayland-only
# builds just leave this off — window/x11.rs and the DRI3 device-id
# fallback in window/compositor.rs are cfg-gated on it.
x11 = ["dep:as-raw-xcb-connection", "dep:tiny-xlib", "dep:x11rb"]
[dependencies]
iced_debug.workspace = true
@ -57,9 +62,9 @@ wayland-protocols.workspace = true
wayland-backend = { version = "0.3.3", features = ["client_system"] }
wayland-client = { version = "0.31.2" }
wayland-sys = { version = "0.31.1", features = ["dlopen"] }
as-raw-xcb-connection = "1.0.1"
tiny-xlib = "0.2.3"
x11rb = { version = "0.13.1", features = [
as-raw-xcb-connection = { version = "1.0.1", optional = true }
tiny-xlib = { version = "0.2.3", optional = true }
x11rb = { version = "0.13.1", optional = true, features = [
"allow-unsafe-code",
"dl-libxcb",
"dri3",

View file

@ -63,7 +63,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
/// Returns the size of the written bytes.
pub fn write(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
offset: usize,
@ -79,7 +78,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
&self.raw,
(offset + bytes_written) as u64,
MAX_WRITE_SIZE_U64,
device,
)
.copy_from_slice(
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
@ -100,7 +98,6 @@ impl<T: bytemuck::Pod> Buffer<T> {
&self.raw,
(offset + bytes_written) as u64,
bytes_left,
device,
)
.copy_from_slice(&bytes[bytes_written..]);

View file

@ -99,7 +99,7 @@ pub fn convert(
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
bind_group_layouts: &[&constant_layout, &texture_layout],
push_constant_ranges: &[],
immediate_size: 0,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
@ -149,7 +149,7 @@ pub fn convert(
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
multiview_mask: None,
cache: None,
});
@ -194,6 +194,7 @@ pub fn convert(
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&pipeline);

View file

@ -132,7 +132,7 @@ impl Atlas {
match &entry {
Entry::Contiguous(allocation) => {
self.upload_allocation(
pixels, width, 0, allocation, device, encoder, belt,
pixels, width, 0, allocation, encoder, belt,
);
}
Entry::Fragmented { fragments, .. } => {
@ -145,7 +145,6 @@ impl Atlas {
width,
offset,
&fragment.allocation,
device,
encoder,
belt,
);
@ -312,7 +311,6 @@ impl Atlas {
image_width: u32,
offset: usize,
allocation: &Allocation,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
) {
@ -334,7 +332,6 @@ impl Atlas {
let buffer_slice = belt.allocate(
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
wgpu::BufferSize::new(8 * 4).unwrap(),
device,
);
const PIXEL: usize = 4;

View file

@ -38,7 +38,10 @@ impl Cache {
raster: Raster {
cache: crate::image::raster::Cache::default(),
pending: HashMap::new(),
belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024),
belt: wgpu::util::StagingBelt::new(
device.clone(),
2 * 1024 * 1024,
),
},
#[cfg(feature = "svg")]
vector: crate::image::vector::Cache::default(),
@ -451,7 +454,10 @@ mod worker {
backend,
texture_layout,
shell: shell.clone(),
belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
belt: wgpu::util::StagingBelt::new(
device.clone(),
4 * 1024 * 1024,
),
jobs: jobs_receiver,
output: work_sender,
quit: quit_receiver,

View file

@ -45,7 +45,7 @@ impl Pipeline {
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Nearest,
mag_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
@ -55,7 +55,7 @@ impl Pipeline {
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
@ -106,8 +106,8 @@ impl Pipeline {
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::image pipeline layout"),
push_constant_ranges: &[],
bind_group_layouts: &[&constant_layout, &texture_layout],
immediate_size: 0,
});
let shader =
@ -191,7 +191,7 @@ impl Pipeline {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});
@ -528,7 +528,6 @@ impl Layer {
&self.uniforms,
0,
(bytes.len() as u64).try_into().expect("Sized uniforms"),
device,
)
.copy_from_slice(bytes);
@ -539,11 +538,11 @@ impl Layer {
let mut offset = 0;
if !nearest.is_empty() {
offset += self.instances.write(device, encoder, belt, 0, nearest);
offset += self.instances.write(encoder, belt, 0, nearest);
}
if !linear.is_empty() {
let _ = self.instances.write(device, encoder, belt, offset, linear);
let _ = self.instances.write(encoder, belt, offset, linear);
}
}

View file

@ -126,6 +126,7 @@ impl Renderer {
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
engine.device.clone(),
buffer::MAX_WRITE_SIZE as u64,
),
@ -454,6 +455,7 @@ impl Renderer {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
},
));
@ -528,6 +530,7 @@ impl Renderer {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
},
));
}
@ -621,6 +624,7 @@ impl Renderer {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
},
));
}

View file

@ -209,7 +209,7 @@ impl Layer {
transformation: Transformation,
scale: f32,
) {
self.update(device, encoder, belt, transformation, scale);
self.update(encoder, belt, transformation, scale);
if !quads.solids.is_empty() {
self.solid.prepare(device, encoder, belt, &quads.solids);
@ -223,7 +223,6 @@ impl Layer {
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
transformation: Transformation,
@ -237,7 +236,6 @@ impl Layer {
&self.constants_buffer,
0,
(bytes.len() as u64).try_into().expect("Sized uniforms"),
device,
)
.copy_from_slice(bytes);
}

View file

@ -51,7 +51,7 @@ impl Layer {
instances: &[Gradient],
) {
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(device, encoder, belt, 0, instances);
let _ = self.instances.write(encoder, belt, 0, instances);
self.instance_count = instances.len();
}
@ -75,8 +75,8 @@ impl Pipeline {
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.quad.gradient.pipeline"),
push_constant_ranges: &[],
bind_group_layouts: &[constants_layout],
immediate_size: 0,
},
);
@ -155,7 +155,7 @@ impl Pipeline {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
},
);

View file

@ -45,7 +45,7 @@ impl Layer {
instances: &[Solid],
) {
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(device, encoder, belt, 0, instances);
let _ = self.instances.write(encoder, belt, 0, instances);
self.instance_count = instances.len();
}
@ -65,8 +65,8 @@ impl Pipeline {
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.quad.solid.pipeline"),
push_constant_ranges: &[],
bind_group_layouts: &[constants_layout],
immediate_size: 0,
});
let shader =
@ -139,7 +139,7 @@ impl Pipeline {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});

View file

@ -619,7 +619,7 @@ fn prepare(
)?;
Some(cryoglyph::TextArea {
buffer,
text: buffer.layout_runs(),
left: position.x,
top: position.y,
scale: transformation.scale_factor()

View file

@ -313,6 +313,7 @@ fn render<'a>(
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
})
};
@ -425,18 +426,13 @@ impl Layer {
let indices = mesh.indices();
index_offset += self.index_buffer.write(
device,
encoder,
belt,
index_offset,
indices,
);
index_offset +=
self.index_buffer
.write(encoder, belt, index_offset, indices);
match mesh {
Mesh::Solid { buffers, .. } => {
solid_vertex_offset += self.solid.vertices.write(
device,
encoder,
belt,
solid_vertex_offset,
@ -444,16 +440,14 @@ impl Layer {
);
solid_uniform_offset += self.solid.uniforms.write(
device,
encoder,
belt,
solid_uniform_offset,
&[uniforms],
);
)
}
Mesh::Gradient { buffers, .. } => {
gradient_vertex_offset += self.gradient.vertices.write(
device,
encoder,
belt,
gradient_vertex_offset,
@ -461,7 +455,6 @@ impl Layer {
);
gradient_uniform_offset += self.gradient.uniforms.write(
device,
encoder,
belt,
gradient_uniform_offset,
@ -731,7 +724,7 @@ mod solid {
&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
immediate_size: 0,
},
);
@ -784,7 +777,7 @@ mod solid {
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
multiview_mask: None,
cache: None,
},
);
@ -886,7 +879,7 @@ mod gradient {
&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
immediate_size: 0,
},
);
@ -949,7 +942,7 @@ mod gradient {
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
multiview_mask: None,
cache: None,
},
);

View file

@ -69,8 +69,8 @@ impl Pipeline {
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::triangle::msaa pipeline layout"),
push_constant_ranges: &[],
bind_group_layouts: &[&constant_layout, &texture_layout],
immediate_size: 0,
});
let shader =
@ -116,7 +116,7 @@ impl Pipeline {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});
@ -177,6 +177,7 @@ impl Pipeline {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
})
}
}
@ -320,7 +321,6 @@ impl State {
0,
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
.expect("non-empty ratio"),
device,
)
.copy_from_slice(bytemuck::bytes_of(&ratio));
@ -351,6 +351,7 @@ impl State {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_pipeline(&pipeline.raw);

View file

@ -7,7 +7,7 @@ pub mod compositor;
not(target_os = "redox")
))]
mod wayland;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))]
mod x11;
pub use compositor::Compositor;

View file

@ -16,7 +16,7 @@ use crate::{Engine, Renderer};
not(target_os = "redox")
))]
use super::wayland::get_wayland_device_ids;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
#[cfg(all(unix, feature = "x11", not(target_os = "macos"), not(target_os = "redox")))]
use super::x11::get_x11_device_ids;
use std::future::Future;
@ -72,22 +72,29 @@ impl Compositor {
not(target_os = "redox")
))]
let ids = compatible_window.as_ref().and_then(|window| {
get_wayland_device_ids(window)
.or_else(|| get_x11_device_ids(window))
let w = get_wayland_device_ids(window);
#[cfg(feature = "x11")]
let w = w.or_else(|| get_x11_device_ids(window));
w
});
#[cfg(all(
unix,
not(feature = "cctk"),
not(target_os = "macos"),
not(target_os = "redox")
))]
let ids = compatible_window
.as_ref()
.and_then(|window| get_x11_device_ids(window));
// HACK:
// 1. If we specifically didn't select an nvidia gpu
// 2. and the user didn't specifically request an nvidia gpu
// 3. and the user didn't set an adapter name,
// 4. and the user didn't request the high power pref
// => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays
#[cfg(all(
unix,
feature = "cctk",
not(target_os = "macos"),
not(target_os = "redox")
))]
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
if !matches!(ids, Some((0x10de, _)))
&& std::env::var_os("__NV_PRIME_RENDER_OFFLOAD")
.is_none_or(|var| var == "0")
@ -126,6 +133,7 @@ impl Compositor {
// if log::max_level() >= log::LevelFilter::Info {
// let available_adapters: Vec<_> = instance
// .enumerate_adapters(settings.backends)
// .await
// .iter()
// .map(wgpu::Adapter::get_info)
// .collect();
@ -148,13 +156,13 @@ impl Compositor {
if std::env::var_os("WGPU_ADAPTER_NAME").is_none() {
#[cfg(all(
unix,
feature = "cctk",
not(target_os = "macos"),
not(target_os = "redox")
))]
if let Some((vendor_id, device_id)) = ids {
adapter = instance
.enumerate_adapters(settings.backends)
.await
.into_iter()
.filter(|adapter| {
let info = adapter.get_info();
@ -172,6 +180,7 @@ impl Compositor {
} else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") {
adapter = instance
.enumerate_adapters(settings.backends)
.await
.into_iter()
.filter(|adapter| {
let info = adapter.get_info();
@ -184,6 +193,8 @@ impl Compositor {
true
}
});
} else {
adapter = instance.request_adapter(&adapter_options).await.ok();
}
let adapter = match adapter {
@ -286,6 +297,14 @@ impl Compositor {
..limits
});
// Request SHADER_F16 only if the adapter supports it (e.g., not available in WebGL2)
let required_features =
if adapter.features().contains(wgpu::Features::SHADER_F16) {
wgpu::Features::SHADER_F16
} else {
wgpu::Features::empty()
};
let mut errors = Vec::new();
for required_limits in limits {
@ -294,7 +313,7 @@ impl Compositor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
),
required_features: wgpu::Features::empty(),
required_features,
required_limits: required_limits.clone(),
memory_hints: wgpu::MemoryHints::MemoryUsage,
trace: wgpu::Trace::Off,
@ -352,6 +371,14 @@ pub fn present(
) -> Result<(), compositor::SurfaceError> {
match surface.get_current_texture() {
Ok(frame) => {
if frame.texture.width() != viewport.physical_width()
|| frame.texture.height() != viewport.physical_height()
{
return Err(compositor::SurfaceError::Outdated);
}
let suboptimal = frame.suboptimal;
let view = &frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
@ -367,6 +394,10 @@ pub fn present(
on_pre_present();
frame.present();
if suboptimal {
return Err(compositor::SurfaceError::Outdated);
}
Ok(())
}
Err(error) => match error {

View file

@ -505,6 +505,7 @@ where
state.raw(),
crate::text::Style {
color: style.text_color,
..crate::text::Style::default()
},
viewport,
);

View file

@ -35,7 +35,7 @@ use crate::core::{
color,
};
use iced_runtime::{Action, Task, task};
use iced_runtime::{Task, task};
/// A widget that aligns its contents inside of its boundaries.
///
@ -601,7 +601,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
operate: &mut dyn FnMut(&mut dyn Operation<Option<Rectangle>>),
) {
self.depth += 1;
self.traverse(operate);
operate(self);
self.depth -= 1;
}

View file

@ -1,7 +1,6 @@
//! Keyed columns distribute content vertically while keeping continuity.
//! Distribute content vertically.
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@ -293,9 +292,12 @@ where
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|((child, state), c_layout)| {
child
.as_widget_mut()
.operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation);
child.as_widget_mut().operate(
state,
c_layout.with_virtual_offset(layout.virtual_offset()),
renderer,
operation,
);
});
});
}
@ -318,7 +320,13 @@ where
.zip(layout.children())
{
child.as_widget_mut().update(
tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell,
tree,
event,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
@ -337,9 +345,13 @@ where
.zip(&tree.children)
.zip(layout.children())
.map(|((child, tree), c_layout)| {
child
.as_widget()
.mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer)
child.as_widget().mouse_interaction(
tree,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()

View file

@ -1,5 +1,5 @@
#![allow(missing_docs)]
use crate::core::event::{self, Event};
use crate::core::event::Event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@ -363,24 +363,25 @@ where
let state = tree.state.downcast_mut::<State>();
let offset = layout.position() - Point::ORIGIN;
self.visible_elements
for (element, (index, layout, tree)) in self
.visible_elements
.iter_mut()
.zip(&mut state.visible_layouts)
.map(|(element, (index, layout, tree))| {
element.as_widget_mut().update(
tree,
event,
Layout::with_offset(
offset + Vector::new(0.0, self.spacing * *index as f32),
layout,
),
cursor,
renderer,
clipboard,
shell,
viewport,
)
});
{
element.as_widget_mut().update(
tree,
event,
Layout::with_offset(
offset + Vector::new(0.0, self.spacing * *index as f32),
layout,
),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
if let Event::Window(window::Event::RedrawRequested(_)) = event {
match &mut state.task {

View file

@ -2,7 +2,6 @@
use iced_renderer::core::mouse::Click;
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;

View file

@ -6,7 +6,7 @@ use crate::core::renderer;
use crate::core::widget::{self, Tree};
use crate::core::{
self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size,
Vector, event,
Vector,
};
use crate::pane_grid::{Draggable, TitleBar};

View file

@ -459,6 +459,7 @@ where
state.raw(),
crate::text::Style {
color: style.text_color,
..crate::text::Style::default()
},
viewport,
);

View file

@ -27,7 +27,6 @@ use iced_runtime::core::widget::Id;
#[cfg(feature = "a11y")]
use std::borrow::Cow;
use crate::core::event;
use crate::core::keyboard;
use crate::core::layout;
use crate::core::mouse;
@ -933,7 +932,7 @@ where
let had_input_method = shell.input_method().is_enabled();
let mut c_event = match event.clone() {
let c_event = match event.clone() {
Event::Dnd(dnd::DndEvent::Offer(
id,
dnd::OfferEvent::Enter {

View file

@ -1,6 +1,5 @@
//! Display content on top of other content.
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;

View file

@ -179,7 +179,10 @@ where
{
let color = color.map(Into::into);
self.style(move |_theme| Style { color })
self.style(move |_theme| Style {
color,
..Style::default()
})
}
/// Sets the default style class of the [`Rich`] text.

View file

@ -544,6 +544,8 @@ pub struct State<Highlighter: text::Highlighter> {
highlighter: RefCell<Highlighter>,
highlighter_settings: Highlighter::Settings,
highlighter_format_address: usize,
context_menu_position: Option<Point>,
pending_edit: Option<Action>,
}
#[derive(Debug, Clone)]
@ -579,6 +581,14 @@ impl<Highlighter: text::Highlighter> State<Highlighter> {
pub fn is_focused(&self) -> bool {
self.focus.is_some()
}
/// Clears focus, selection, and all interaction state.
pub fn clear_focus(&mut self) {
self.focus = None;
self.drag_click = None;
self.context_menu_position = None;
self.pending_edit = Some(Action::ClearSelection);
}
}
impl<Highlighter: text::Highlighter> operation::Focusable
@ -593,7 +603,7 @@ impl<Highlighter: text::Highlighter> operation::Focusable
}
fn unfocus(&mut self) {
self.focus = None;
self.clear_focus();
}
}
@ -621,6 +631,8 @@ where
)),
highlighter_settings: self.highlighter_settings.clone(),
highlighter_format_address: self.highlighter_format as usize,
context_menu_position: None,
pending_edit: None,
})
}
@ -699,6 +711,58 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) {
// Handle context menu and focus-loss on clicks outside.
{
let state = tree.state.downcast_mut::<State<Highlighter>>();
let text_bounds = layout.bounds();
if matches!(
event,
Event::Mouse(mouse::Event::ButtonPressed(_))
| Event::Touch(
crate::core::touch::Event::FingerPressed { .. }
)
) && cursor.position_over(text_bounds).is_none()
{
state.clear_focus();
}
match event {
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Right,
)) => {
if let Some(pos) = cursor.position_over(text_bounds) {
if state.focus.is_none() {
state.focus = Some(Focus::now());
}
state.context_menu_position = Some(pos);
shell.capture_event();
return;
}
}
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
)) => {
if state.context_menu_position.take().is_some() {
shell.capture_event();
return;
}
}
_ => {}
}
}
// Process pending edits from context menu
// publishes via on_edit so the app calls content.perform().
{
let state = tree.state.downcast_mut::<State<Highlighter>>();
if let Some(action) = state.pending_edit.take() {
if let Some(on_edit) = self.on_edit.as_ref() {
shell.publish((on_edit)(action));
}
}
}
let Some(on_edit) = self.on_edit.as_ref() else {
return;
};
@ -1223,6 +1287,23 @@ impl<Message> Binding<Message> {
return Some(binding);
}
let alt_clipboard = match key.as_ref() {
keyboard::Key::Named(key::Named::Insert) if modifiers.shift() => {
Some(Self::Paste)
}
keyboard::Key::Named(key::Named::Insert) if modifiers.command() => {
Some(Self::Copy)
}
keyboard::Key::Named(key::Named::Delete) if modifiers.shift() => {
Some(Self::Cut)
}
_ => None,
};
if let Some(binding) = alt_clipboard {
return Some(binding);
}
#[cfg(target_os = "macos")]
let modified_key =
convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
@ -1545,3 +1626,63 @@ pub(crate) fn convert_macos_shortcut(
Some(keyboard::Key::Named(key))
}
use crate::core::widget::text::HasSelectableText;
impl<Highlighter, Message, Theme, Renderer> HasSelectableText
for TextEditor<'_, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Theme: Catalog,
Renderer: text::Renderer,
{
fn selected_text(&self, _tree: &widget::Tree) -> Option<String> {
self.content.selection()
}
fn select_all(&self, tree: &mut widget::Tree) {
let state = tree.state.downcast_mut::<State<Highlighter>>();
state.pending_edit = Some(Action::SelectAll);
}
fn is_editable(&self) -> bool {
self.on_edit.is_some()
}
fn 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())
}
}

View file

@ -1,6 +1,5 @@
use crate::container;
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;

View file

@ -37,7 +37,6 @@ use std::borrow::Cow;
use iced_runtime::core::border::Radius;
use crate::core::alignment;
use crate::core::border;
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@ -483,6 +482,7 @@ where
state.raw(),
crate::text::Style {
color: style.text_color,
..crate::text::Style::default()
},
viewport,
);
@ -509,7 +509,7 @@ where
let space = style.handle_margin;
let toggler_background_bounds = Rectangle {
let _toggler_background_bounds = Rectangle {
x: bounds.x,
y: bounds.y,
width: bounds.width,
@ -529,7 +529,7 @@ where
style.background,
);
let padding = (style.padding_ratio * bounds.height).round();
let _padding = (style.padding_ratio * bounds.height).round();
let toggler_foreground_bounds = Rectangle {
x: bounds.x
+ if self.is_toggled {

View file

@ -37,6 +37,8 @@ cctk = [
"xkeysym",
"dep:cctk",
]
# Prevents multiple separate process instances.
single-instance = []
[dependencies]

View file

@ -178,7 +178,7 @@ pub fn window_attributes(
),
));
}
#[cfg(all(feature = "cctk", target_os = "linux"))]
#[cfg(target_os = "linux")]
{
use winit::platform::wayland::WindowAttributesWayland;
@ -387,19 +387,19 @@ pub fn window_event(
self::modifiers(new_modifiers.state()),
)))
}
WindowEvent::Ime(event) => Some(Event::InputMethod(match event {
Ime::Enabled => input_method::Event::Opened,
Ime::Preedit(content, size) => input_method::Event::Preedit(
WindowEvent::Ime(event) => match event {
Ime::Enabled => Some(input_method::Event::Opened),
Ime::Preedit(content, size) => Some(input_method::Event::Preedit(
content,
size.map(|(start, end)| start..end),
),
Ime::Commit(content) => input_method::Event::Commit(content),
Ime::Disabled => input_method::Event::Closed,
Ime::DeleteSurrounding {
before_bytes,
after_bytes,
} => todo!(),
})),
)),
Ime::Commit(content) => Some(input_method::Event::Commit(content)),
Ime::Disabled => Some(input_method::Event::Closed),
// Upstream iced depends on the winit version which doesn't support
// this kind of ime event.
Ime::DeleteSurrounding { .. } => None,
}
.map(Event::InputMethod),
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
window::Event::Focused
} else {

View file

@ -40,12 +40,6 @@ pub mod clipboard;
pub mod conversion;
pub mod platform_specific;
#[cfg(feature = "program")]
pub mod program;
#[cfg(feature = "system")]
pub mod system;
mod error;
mod proxy;
mod window;
@ -192,11 +186,15 @@ where
receiver: mpsc::UnboundedReceiver<Control>,
error: Option<Error>,
system_theme: Option<oneshot::Sender<theme::Mode>>,
control_sender: mpsc::UnboundedSender<Control>,
#[cfg(feature = "a11y")]
adapters: std::collections::HashMap<window::Id, (u64, iced_accessibility::accesskit_winit::Adapter)>,
control_sender: mpsc::UnboundedSender<Control>,
#[cfg(feature = "a11y")]
adapters: std::collections::HashMap<
window::Id,
(u64, iced_accessibility::accesskit_winit::Adapter),
>,
#[cfg(target_arch = "wasm32")]
is_booted: std::rc::Rc<std::cell::RefCell<bool>>,
#[cfg(target_arch = "wasm32")]
@ -215,6 +213,7 @@ where
id: settings.id,
sender: event_sender,
receiver: control_receiver,
#[cfg(feature = "a11y")]
control_sender: control_sender.clone(),
error: None,
system_theme: Some(system_theme_sender),
@ -501,9 +500,15 @@ where
}
};
}
let window: Arc<dyn winit::window::Window + 'static> = Arc::from(window);
let window: Arc<
dyn winit::window::Window + 'static,
> = Arc::from(window);
#[cfg(feature = "a11y")]
self.init_adapter(event_loop, id, window.clone());
self.init_adapter(
event_loop,
id,
window.clone(),
);
self.process_event(
event_loop,
@ -572,11 +577,13 @@ where
.expect("Send event");
}
Control::Winit(id, e) => {
#[cfg(all(feature = "cctk", target_os = "linux"))]
#[cfg(all(
feature = "cctk",
target_os = "linux"
))]
{
if matches!(e, WindowEvent::RedrawRequested)
{
{
for id in crate::subsurface_widget::subsurface_ids(id) {
_ = self.sender
.unbounded_send(Event::Winit(
@ -597,7 +604,7 @@ where
}
}
}
self.sender
.start_send(Event::Winit(id, e))
.expect("Send event");
@ -610,7 +617,7 @@ where
#[cfg(feature = "a11y")]
Control::InitAdapter(id, window) => {
self.init_adapter(event_loop, id, window);
self.process_event(
event_loop,
Some(Event::A11yAdapter(id)),
@ -618,8 +625,8 @@ where
}
#[cfg(feature = "a11y")]
Control::Cleanup(id) => {
_ = self.adapters.remove(&id);
},
_ = self.adapters.remove(&id);
}
},
_ => {
break;
@ -633,34 +640,35 @@ where
}
}
#[cfg(feature = "a11y")]
fn init_adapter(&mut self, event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static), id: core::window::Id, window: Arc<dyn winit::window::Window + 'static>) {
#[cfg(feature = "a11y")]
fn init_adapter(
&mut self,
event_loop: &(dyn winit::event_loop::ActiveEventLoop + 'static),
id: core::window::Id,
window: Arc<dyn winit::window::Window + 'static>,
) {
use crate::a11y::*;
use iced_accessibility::accesskit::{
ActivationHandler, Node, NodeId, Role,
Tree, TreeUpdate,
ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
};
use iced_accessibility::accesskit_winit::Adapter;
let node_id =
iced_runtime::core::id::window_node_id();
let activation_handler =
WinitActivationHandler {
proxy: self.control_sender.clone(),
title: String::new(),
};
let node_id = iced_runtime::core::id::window_node_id();
let activation_handler = WinitActivationHandler {
proxy: self.control_sender.clone(),
title: String::new(),
};
let action_handler = WinitActionHandler {
id,
proxy: self.control_sender.clone(),
};
let deactivation_handler =
WinitDeactivationHandler {
proxy: self.control_sender.clone(),
};
let deactivation_handler = WinitDeactivationHandler {
proxy: self.control_sender.clone(),
};
_ = self.adapters.insert(
id,
(
@ -674,8 +682,6 @@ where
),
),
);
}
}
@ -838,6 +844,28 @@ async fn run_instance<P>(
log::info!("System theme: {system_theme:?}");
macro_rules! run_action {
($action:expr) => {
run_action(
$action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
&mut platform_specific_handler,
is_daemon,
)
};
}
'next_event: loop {
// Empty the queue if possible
let event = if let Ok(event) = event_receiver.try_recv() {
@ -857,35 +885,36 @@ async fn run_instance<P>(
exit_on_close_request,
make_visible,
on_open,
resize_border
resize_border,
} => {
#[cfg(all(feature = "cctk", target_os = "linux"))]
platform_specific_handler.send_wayland(
platform_specific::Action::TrackWindow(window.clone(), id),
);
match create_compositor::<P>(
window.clone(),
CreateCompositor {
proxy: &proxy,
runtime: &mut runtime,
display_handle: &display_handle,
graphics_settings: &graphics_settings,
default_fonts: &default_fonts,
},
)
.await
{
Ok(comp) => {
compositor = Some(comp);
}
Err(error) => {
control_sender
.start_send(Control::Crash(
Error::GraphicsCreationFailed(error),
))
.expect("Send control message");
continue;
if compositor.is_none() {
match create_compositor::<P>(
window.clone(),
CreateCompositor {
proxy: &proxy,
runtime: &mut runtime,
display_handle: &display_handle,
graphics_settings: &graphics_settings,
default_fonts: &default_fonts,
},
)
.await
{
Ok(comp) => {
compositor = Some(comp);
}
Err(error) => {
control_sender
.start_send(Control::Crash(
Error::GraphicsCreationFailed(error),
))
.expect("Send control message");
continue;
}
}
}
@ -913,7 +942,7 @@ async fn run_instance<P>(
compositor,
exit_on_close_request,
system_theme,
resize_border
resize_border,
);
window.raw.set_theme(conversion::window_theme(
@ -999,22 +1028,7 @@ async fn run_instance<P>(
}
}
Event::UserEvent(action) => {
let exited = run_action(
action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
&mut platform_specific_handler,
);
let exited = run_action!(action);
if exited {
runtime.track(None.into_iter());
}
@ -1025,7 +1039,7 @@ async fn run_instance<P>(
continue;
};
let Some((id, mut window)) =
let Some((id, mut window)) =
window_manager.get_mut_alias(window_id)
else {
continue;
@ -1040,12 +1054,12 @@ async fn run_instance<P>(
))
.expect("Send redraw event");
continue;
}
}
// XX must force update to corner radius before the surface is committed.
#[cfg(all(feature = "cctk", target_os = "linux"))]
if (window.surface_version != window.state.surface_version()
|| window.logical_size() != window.state.logical_size()
) && !crate::subsurface_widget::is_subsurface(window_id)
|| window.logical_size() != window.state.logical_size())
&& !crate::subsurface_widget::is_subsurface(window_id)
{
platform_specific_handler.send_wayland(
platform_specific::Action::ResizeWindow(id),
@ -1156,22 +1170,7 @@ async fn run_instance<P>(
continue;
}
_ = run_action(
action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
&mut platform_specific_handler,
);
_ = run_action!(action);
}
for (window_id, window) in window_manager.iter_mut() {
@ -1326,9 +1325,7 @@ async fn run_instance<P>(
continue;
};
// Initiates a drag resize window state when found.
if let Some(func) =
window.drag_resize_window_func.as_mut()
{
if let Some(func) = window.drag_resize_window_func.as_mut() {
if func(window.raw.as_ref(), &event) {
continue;
}
@ -1354,22 +1351,9 @@ async fn run_instance<P>(
if matches!(event, winit::event::WindowEvent::CloseRequested)
&& window.exit_on_close_request
{
_ = run_action(
Action::Window(runtime::window::Action::Close(id)),
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
&mut platform_specific_handler,
);
_ = run_action!(Action::Window(
runtime::window::Action::Close(id)
));
} else {
window.state.update(&program, window.raw.as_ref(), &event);
@ -1547,22 +1531,7 @@ async fn run_instance<P>(
));
for action in actions {
let exited = run_action(
action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
&mut platform_specific_handler,
);
let exited = run_action!(action);
if exited {
runtime.track(None.into_iter());
}
@ -1772,7 +1741,6 @@ async fn run_instance<P>(
};
// search windows for widget with operation
if result.is_none() {
log::warn!(
"start_dnd: widget {:?} not found; drag will fail",
@ -2106,6 +2074,7 @@ fn run_action<'a, P, C>(
is_window_opening: &mut bool,
system_theme: &mut theme::Mode,
platform_specific: &mut crate::platform_specific::PlatformSpecific,
is_daemon: bool,
) -> bool
where
P: Program,
@ -2148,7 +2117,7 @@ where
on_open: channel,
})
.expect("Send control action");
*is_window_opening = true;
}
window::Action::Close(id) => {
@ -2190,7 +2159,7 @@ where
));
}
if window_manager.is_empty() {
if window_manager.is_empty() && !is_daemon {
*compositor = None;
}
}

View file

@ -153,9 +153,12 @@ impl PlatformSpecific {
) -> Option<Box<dyn HasWindowHandle + Send + Sync + 'static>> {
#[cfg(all(feature = "cctk", target_os = "linux"))]
{
return self.wayland.create_surface();
self.wayland.create_surface()
}
#[cfg(not(all(feature = "cctk", target_os = "linux")))]
{
None
}
None
}
pub(crate) fn update_surface_shm(

View file

@ -1,8 +1,8 @@
use crate::core::window::Id as SurfaceId;
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{self, wayland},
task, Action, Task,
task,
};
pub fn request_token(

View 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),
))
})
}

View file

@ -1,11 +1,11 @@
use iced_futures::core::window;
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{
self,
wayland::{self, CornerRadius},
},
task, Action, Task,
task,
};
pub fn corner_radius(

View file

@ -1,7 +1,7 @@
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{self, wayland},
task, Action, Task,
task,
};
pub fn inhibit_shortcuts(inhibit: bool) -> Task<()> {

View file

@ -2,7 +2,7 @@
use crate::core::window::Id as SurfaceId;
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{
self,
wayland::{
@ -10,7 +10,7 @@ use iced_runtime::{
layer_surface::{IcedMargin, SctkLayerSurfaceSettings},
},
},
task, Action, Task,
task,
};
pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
@ -114,3 +114,14 @@ 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 },
)),
))
}

View file

@ -1,6 +1,7 @@
//! Interact with the wayland objects of your application.
pub mod activation;
pub mod blur;
pub mod corner_radius;
pub mod keyboard_shortcuts_inhibit;
pub mod layer_surface;

View file

@ -1,7 +1,8 @@
use iced_futures::core::window::Id;
use iced_runtime::{
Action, Task,
platform_specific::{self, wayland},
task, Action, Task,
task,
};
/// Request subscription for overlap notification events on the surface

View file

@ -1,12 +1,15 @@
//! Interact with the popups of your application.
use crate::core::window::Id as SurfaceId;
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{
self,
wayland::{self, popup::SctkPopupSettings},
wayland::{
self,
popup::{SctkPopupSettings, SctkPositioner},
},
},
task, Action, Task,
task,
};
/// <https://wayland.app/protocols/wlr-layer-shell-unstable-v1#zwlr_layer_surface_v1:request:get_popup>
@ -32,6 +35,18 @@ pub fn set_size<Message>(
))
}
/// <https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition>
pub fn reposition<Message>(
id: SurfaceId,
positioner: SctkPositioner,
) -> Task<Message> {
task::effect(Action::PlatformSpecific(
platform_specific::Action::Wayland(wayland::Action::Popup(
wayland::popup::Action::Reposition { id, positioner },
)),
))
}
/// <https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy>
pub fn destroy_popup<Message>(id: SurfaceId) -> Task<Message> {
task::effect(Action::PlatformSpecific(

View file

@ -1,9 +1,9 @@
use crate::core::window::Id as SurfaceId;
use cctk::sctk::reexports::client::protocol::wl_output::WlOutput;
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{self, wayland},
task, Action, Task,
task,
};
pub fn lock<Message>() -> Task<Message> {

View file

@ -1,12 +1,12 @@
use crate::core::window::Id as SurfaceId;
pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
use iced_runtime::{
self,
self, Action, Task,
platform_specific::{
self,
wayland::{self, subsurface::SctkSubsurfaceSettings},
},
task, Action, Task,
task,
};
pub fn get_subsurface<Message>(

View file

@ -7,7 +7,10 @@ use crate::platform_specific::SurfaceIdWrapper;
use crate::{
Control,
futures::futures::channel::mpsc,
handlers::{overlap::OverlapNotifyV1, text_input::TextInputManager},
handlers::{
ext_background_effect, overlap::OverlapNotifyV1,
text_input::TextInputManager,
},
platform_specific::wayland::{
handlers::{
wp_fractional_scaling::FractionalScalingManager,
@ -30,7 +33,7 @@ use cctk::{
reexports::{
calloop::{self, EventLoop},
client::{
ConnectError, Connection, Proxy, globals::registry_queue_init,
Connection, Proxy, globals::registry_queue_init,
},
},
registry::RegistryState,
@ -43,8 +46,6 @@ use cctk::{
};
use raw_window_handle::HasDisplayHandle;
use state::{FrameStatus, SctkWindow, send_event};
#[cfg(feature = "a11y")]
use std::sync::{Arc, Mutex};
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
@ -68,8 +69,8 @@ pub struct SctkEventLoop {
pub(crate) state: SctkState,
}
#[derive(Debug)]
pub enum Error {
Connect(ConnectError),
Calloop(calloop::Error),
Global(GlobalError),
NoDisplayHandle,
@ -126,7 +127,7 @@ impl SctkEventLoop {
crate::platform_specific::Action::ResizeWindow(id) => {
if let Some((_, v)) = state.windows.iter()
.find(|w| w.id == id)
.map(|w| w.corner_radius.as_ref())
.map(|w| state.corner_radii.get(&id))
.unwrap_or_default() {
_ = state.handle_action(iced_runtime::platform_specific::wayland::Action::RoundedCorners(id, *v));
}
@ -135,7 +136,7 @@ impl SctkEventLoop {
window,
id,
) => {
state.windows.push(SctkWindow { window, id, corner_radius: Default::default() });
state.windows.push(SctkWindow { window, id });
if let Some(v) = state.pending_corner_radius.remove(&id) {
_ = state.handle_action(iced_runtime::platform_specific::wayland::Action::RoundedCorners(id, Some(v)));
}
@ -346,26 +347,26 @@ impl SctkEventLoop {
&qh,
),
corner_radius_manager: registry_state.bind_one::<CosmicCornerRadiusManagerV1, _, _>(
&qh,
1..=1,
(),
).ok(),
&qh,
1..=2,
(),
).ok(),
toplevel_manager: ToplevelManagerState::try_new(
&registry_state,
&qh,
),
inhibitor_manager: registry_state.bind_one::<zwp_keyboard_shortcuts_inhibit_manager_v1::ZwpKeyboardShortcutsInhibitManagerV1, _, _>(
&qh,
1..=1,
(),
).ok(),
inhibitor_manager: registry_state.bind_one::<zwp_keyboard_shortcuts_inhibit_manager_v1::ZwpKeyboardShortcutsInhibitManagerV1, _, _>(
&qh,
1..=1,
(),
).ok(),
text_input_manager: TextInputManager::try_new(&registry_state, &qh),
ext_background_effect_manager: ext_background_effect::ExtBackgroundEffectManager::new(&globals, &qh).ok(),
registry_state,
queue_handle: qh,
loop_handle,
inhibitor: None,
inhibited: false,
_cursor_surface: None,
@ -373,6 +374,8 @@ impl SctkEventLoop {
outputs: Vec::new(),
seats: Vec::new(),
windows: Vec::new(),
blur_surfaces: HashMap::new(),
corner_radii: HashMap::new(),
layer_surfaces: Vec::new(),
popups: Vec::new(),
lock_surfaces: Vec::new(),
@ -398,6 +401,7 @@ impl SctkEventLoop {
preedit: None,
pending_delete: None,
pending_commit: None,
pending_blur: HashMap::new(),
},
_features: Default::default(),
};
@ -484,6 +488,23 @@ impl SctkEventLoop {
let had_events = !state.state.sctk_events.is_empty();
let mut wake_up = had_events;
for e in state.state.sctk_events.drain(..) {
if let SctkEvent::Winit(id, e) = e {
_ = state
.state
.events_sender
.unbounded_send(Control::Winit(id, e));
} else {
_ =
state
.state
.events_sender
.unbounded_send(Control::PlatformSpecific(
crate::platform_specific::Event::Wayland(e),
));
}
}
for s in
state
.state
@ -528,22 +549,6 @@ impl SctkEventLoop {
);
}
for e in state.state.sctk_events.drain(..) {
if let SctkEvent::Winit(id, e) = e {
_ = state
.state
.events_sender
.unbounded_send(Control::Winit(id, e));
} else {
_ =
state
.state
.events_sender
.unbounded_send(Control::PlatformSpecific(
crate::platform_specific::Event::Wayland(e),
));
}
}
if wake_up {
state.state.proxy.wake_up();
}

View file

@ -1,8 +1,8 @@
use cctk::sctk::reexports::calloop;
use iced_futures::futures::{
Sink,
channel::mpsc,
task::{Context, Poll},
Sink,
};
use std::pin::Pin;

View file

@ -2,7 +2,9 @@ use crate::{
Control,
handlers::{
activation::IcedRequestData,
ext_background_effect,
overlap::{OverlapNotificationV1, OverlapNotifyV1},
shell::corner_radius::CornerRadiusWrapper,
text_input::{Preedit, TextInputManager},
},
platform_specific::{
@ -25,11 +27,10 @@ use iced_futures::{
};
use raw_window_handle::HasWindowHandle;
use std::{
collections::{HashMap, HashSet},
collections::{HashMap, HashSet, hash_map::Entry},
convert::Infallible,
fmt::Debug,
sync::{Arc, Mutex, atomic::AtomicU32},
thread::panicking,
time::Duration,
};
use wayland_backend::client::ObjectId;
@ -40,17 +41,13 @@ use winit::{
use cctk::{
cosmic_protocols::{
corner_radius::v1::client::{
cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
},
corner_radius::v1::client::cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1,
},
sctk::{
activation::{ActivationState, RequestData},
compositor::CompositorState,
error::GlobalError,
globals::GlobalData,
output::OutputState,
reexports::{
calloop::{LoopHandle, timer::TimeoutAction},
@ -66,11 +63,12 @@ use cctk::{
wl_touch::WlTouch,
},
},
protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
},
registry::RegistryState,
seat::{
SeatState,
keyboard::KeyEvent,
keyboard::{KeyEvent, Modifiers},
pointer::{CursorIcon, PointerData, ThemedPointer},
touch::TouchData,
},
@ -96,7 +94,6 @@ use cctk::{
};
use iced_runtime::{
core::{self, Point, touch},
keyboard::Modifiers,
platform_specific::{
self,
wayland::{
@ -108,6 +105,10 @@ use iced_runtime::{
},
};
use wayland_protocols::{
ext::background_effect::v1::client::{
ext_background_effect_manager_v1,
ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1,
},
wp::{
fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1,
keyboard_shortcuts_inhibit::zv1::client::{
@ -341,22 +342,12 @@ pub struct SctkPopupData {
pub(crate) grab: bool,
}
#[derive(Debug)]
pub struct MyCosmicCornerRadiusToplevelV1(CosmicCornerRadiusToplevelV1);
impl Drop for MyCosmicCornerRadiusToplevelV1 {
fn drop(&mut self) {
self.0.destroy();
}
}
#[derive(Debug, Clone)]
pub struct SctkCornerRadius(Arc<MyCosmicCornerRadiusToplevelV1>);
pub struct SctkCornerRadius(Arc<CornerRadiusWrapper>);
pub struct SctkWindow {
pub(crate) window: Arc<dyn winit::window::Window>,
pub(crate) id: core::window::Id,
pub(crate) corner_radius: Option<(SctkCornerRadius, Option<CornerRadius>)>,
}
impl SctkWindow {
@ -443,6 +434,8 @@ pub struct SctkState {
pub(crate) popups: Vec<SctkPopup>,
pub(crate) subsurfaces: Vec<SctkSubsurface>,
pub(crate) lock_surfaces: Vec<SctkLockSurface>,
pub(crate) blur_surfaces: HashMap<core::window::Id, ExtBackgroundEffectSurfaceV1>,
pub(crate) corner_radii: HashMap<core::window::Id, (SctkCornerRadius, Option<CornerRadius>)>,
pub(crate) touch_points: HashMap<touch::Finger, (WlSurface, Point)>,
/// Window updates, which are coming from SCTK or the compositor, which require
@ -485,6 +478,7 @@ pub struct SctkState {
pub(crate) toplevel_info: Option<ToplevelInfoState>,
pub(crate) toplevel_manager: Option<ToplevelManagerState>,
pub(crate) subsurface_state: Option<SubsurfaceState>,
pub(crate) ext_background_effect_manager: Option<ext_background_effect::ExtBackgroundEffectManager>,
pub(crate) activation_token_ctr: u32,
pub(crate) token_senders: HashMap<u32, oneshot::Sender<Option<String>>>,
@ -495,6 +489,7 @@ pub struct SctkState {
pub(crate) corner_radius_manager: Option<CosmicCornerRadiusManagerV1>,
pub(crate) pending_corner_radius: HashMap<core::window::Id, CornerRadius>,
pub(crate) pending_blur: HashMap<core::window::Id, Vec<Rectangle>>,
pub(crate) text_input_manager: Option<TextInputManager>,
pub(crate) text_input: Option<Arc<ZwpTextInputV3>>,
@ -506,6 +501,10 @@ pub struct SctkState {
/// An error that occurred while running an application.
#[derive(Debug, thiserror::Error)]
pub enum PopupCreationError {
/// The requested ID already exists.
#[error("The requested ID already exists")]
AlreadyExists,
/// Positioner creation failed
#[error("Positioner creation failed")]
PositionerCreationFailed(GlobalError),
@ -526,6 +525,10 @@ pub enum PopupCreationError {
/// An error that occurred while running an application.
#[derive(Debug, thiserror::Error)]
pub enum LayerSurfaceCreationError {
/// The requested ID already exists.
#[error("The requested ID already exists")]
AlreadyExists,
/// Layer shell is not supported by the compositor
#[error("Layer shell is not supported by the compositor")]
LayerShellNotSupported,
@ -542,6 +545,10 @@ pub enum LayerSurfaceCreationError {
/// An error that occurred while running an application.
#[derive(Debug, thiserror::Error)]
pub enum SubsurfaceCreationError {
/// The requested ID already exists.
#[error("The requested ID already exists")]
AlreadyExists,
/// Subsurface creation failed
#[error("Subsurface creation failed")]
CreationFailed(GlobalError),
@ -666,9 +673,7 @@ impl SctkState {
// TODO winit sets cursor size after handling the change for the window, so maybe that should be done as well.
}
}
impl SctkState {
pub fn get_popup(
&mut self,
settings: SctkPopupSettings,
@ -682,6 +687,18 @@ impl SctkState {
),
PopupCreationError,
> {
if self.layer_surfaces.iter().any(|s| s.id == settings.id)
|| self.windows.iter().any(|w| w.id == settings.id)
|| self.popups.iter().any(|p| p.data.id == settings.id)
|| self.subsurfaces.iter().any(|s| s.id == settings.id)
{
log::warn!(
"Popup surface with id {:?} already exists",
settings.id
);
return Err(PopupCreationError::AlreadyExists);
}
let (parent, toplevel) = if let Some(parent) =
self.layer_surfaces.iter().find(|l| l.id == settings.parent)
{
@ -823,6 +840,7 @@ impl SctkState {
)
}
};
let toplevel = toplevel.clone();
popup.xdg_surface().set_window_geometry(
0,
@ -855,7 +873,10 @@ impl SctkState {
}
_ = wl_surface.frame(&self.queue_handle, wl_surface.clone());
wl_surface.commit();
if let Some(blur) = self.pending_blur.remove(&settings.id) {
self.apply_blur(settings.id, Some(blur), &wl_surface);
}
let wp_viewport = self.viewporter_state.as_ref().map(|state| {
let viewport =
@ -888,6 +909,13 @@ impl SctkState {
close_with_children: settings.close_with_children,
});
if let Some(corners) = self.pending_corner_radius.remove(&settings.id) {
self.handle_action(Action::RoundedCorners(
settings.id,
Some(corners),
));
}
wl_surface.commit();
Ok((
settings.id,
parent.wl_surface().clone(),
@ -922,6 +950,15 @@ impl SctkState {
IcedOutput::Output(output) => Some(output),
};
if self.layer_surfaces.iter().any(|s| s.id == id)
|| self.windows.iter().any(|w| w.id == id)
|| self.popups.iter().any(|p| p.data.id == id)
|| self.subsurfaces.iter().any(|s| s.id == id)
{
log::warn!("Layer surface with id {id:?} already exists");
return Err(LayerSurfaceCreationError::AlreadyExists);
}
let layer_shell = self
.layer_shell
.as_ref()
@ -974,7 +1011,9 @@ impl SctkState {
layer_surface.set_input_region(Some(&region));
region.destroy();
}
layer_surface.commit();
if let Some(blur) = self.pending_blur.remove(&id) {
self.apply_blur(id, Some(blur), &wl_surface);
}
let wp_viewport = self.viewporter_state.as_ref().map(|state| {
state.get_viewport(layer_surface.wl_surface(), &self.queue_handle)
@ -1008,6 +1047,10 @@ impl SctkState {
wp_fractional_scale,
common: common.clone(),
});
if let Some(corners) = self.pending_corner_radius.remove(&id) {
_ = self.handle_action(Action::RoundedCorners(id, Some(corners)));
}
layer_surface.commit();
Ok((id, CommonSurface::Layer(layer_surface), common))
}
pub fn get_lock_surface(
@ -1015,6 +1058,14 @@ impl SctkState {
id: core::window::Id,
output: &WlOutput,
) -> Option<(CommonSurface, Arc<Mutex<Common>>)> {
if self.layer_surfaces.iter().any(|s| s.id == id)
|| self.windows.iter().any(|w| w.id == id)
|| self.popups.iter().any(|p| p.data.id == id)
|| self.subsurfaces.iter().any(|s| s.id == id)
{
log::warn!("Lock surface with id {id:?} already exists");
return None;
}
if let Some(lock) = self.session_lock.as_ref() {
let wl_surface =
self.compositor_state.create_surface(&self.queue_handle);
@ -1088,9 +1139,20 @@ impl SctkState {
}
},
platform_specific::wayland::layer_surface::Action::Destroy(id) => {
if let Some(p) = self
.popups
.iter().find_map(|p|
self.layer_surfaces.iter().find_map(|l| if l.id == id && l.surface.wl_surface() == p.data.parent.wl_surface() {Some(p.data.id)} else {None})) {
_ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy { id: p }));
}
if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) {
let l = self.layer_surfaces.remove(i);
if let Some(blurred) = self.blur_surfaces.remove(&l.id) {
blurred.destroy();
}
_ = self.corner_radii.remove(&id);
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
@ -1112,6 +1174,7 @@ impl SctkState {
if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) {
_ = self.destroyed.insert(destroyed);
}
send_event(&self.events_sender, &self.proxy, SctkEvent::LayerSurfaceEvent {
variant: LayerSurfaceEventVariant::Done,
id: l.surface.wl_surface().clone(),
@ -1186,6 +1249,14 @@ impl SctkState {
}
},
platform_specific::wayland::layer_surface::Action::Padding { id, padding } => {
// corner radius padding
if let Some((protocol_object, _)) = self.corner_radii.get_mut(&id) {
if let CornerRadiusWrapper::Wlr(protocol_object) = protocol_object.0.as_ref() {
protocol_object.set_padding(padding.top, padding.right, padding.bottom, padding.left);
}
}
},
},
Action::Popup(action) => match action {
platform_specific::wayland::popup::Action::Popup { popup: settings } => {
@ -1290,7 +1361,6 @@ impl SctkState {
}
});
}
// log::error!("Invalid popup Id {:?}", popup.id);
} else {
self.pending_popup = None;
match self.get_popup(settings) {
@ -1344,6 +1414,12 @@ impl SctkState {
_ = self.destroyed.insert(id);
}
if let Some(blurred) = self.blur_surfaces.remove(&id) {
blurred.destroy();
}
_ = self.corner_radii.remove(&id);
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
@ -1379,6 +1455,48 @@ impl SctkState {
SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface });
}
},
platform_specific::wayland::popup::Action::Reposition { id, positioner } => {
if let Some(sctk_popup) = self
.popups
.iter_mut()
.find(|s| s.data.id == id)
{
sctk_popup.data.positioner.set_anchor(positioner.anchor);
sctk_popup.data.positioner.set_anchor_rect(
positioner.anchor_rect.x,
positioner.anchor_rect.y,
positioner.anchor_rect.width,
positioner.anchor_rect.height,
);
if let Ok(constraint_adjustment) =
positioner.constraint_adjustment.try_into()
{
sctk_popup.data.positioner.set_constraint_adjustment(constraint_adjustment);
}
sctk_popup.data.positioner.set_gravity(positioner.gravity);
sctk_popup.data.positioner.set_offset(
positioner.offset.0,
positioner.offset.1,
);
if positioner.reactive {
sctk_popup.data.positioner.set_reactive();
}
let guard =sctk_popup.common.lock().unwrap();
let w = guard.size.width;
let h = guard.size.height;
drop(guard);
let size = positioner.size.unwrap_or((w, h));
sctk_popup.popup
.xdg_surface()
.set_window_geometry(0, 0, w as i32, h as i32);
sctk_popup.update_viewport(w, h);
// update positioner
sctk_popup.data.positioner.set_size(w as i32, h as i32);
sctk_popup.popup.reposition(&sctk_popup.data.positioner, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); let surface = sctk_popup.popup.wl_surface().clone();
_ = send_event(&self.events_sender, &self.proxy,
SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface });
}
},
},
Action::Activation(activation_event) => match activation_event {
platform_specific::wayland::activation::Action::RequestToken { app_id, window, channel } => {
@ -1455,6 +1573,7 @@ impl SctkState {
// TODO how to handle this when there's no lock?
if let Some((surface, _)) = self.get_lock_surface(id, &output) {
let wl_surface = surface.wl_surface();
receive_frame(&mut self.frame_status, &wl_surface);
}
}
@ -1465,6 +1584,11 @@ impl SctkState {
})
{
let surface = self.lock_surfaces.remove(i);
if let Some(blurred) = self.blur_surfaces.remove(&surface.id) {
blurred.destroy();
}
_ = self.corner_radii.remove(&id);
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
@ -1504,7 +1628,7 @@ impl SctkState {
};
let notification = overlap_notify_state.notify.notify_on_overlap(wlr, &self.queue_handle, OverlapNotificationV1 { surface: layer_surface.surface.wl_surface().clone() });
_ = self.overlap_notifications.insert(my_id, notification);
} else {
} else if !enabled {
_ = self.overlap_notifications.remove(&my_id);
}
} else {
@ -1536,7 +1660,7 @@ impl SctkState {
let mut destroyed = vec![];
if let Some(subsurface) = self.subsurfaces.iter().position(|s| s.id == id) {
let subsurface = self.subsurfaces.remove(subsurface);
destroyed.push((subsurface.instance.wl_surface.clone(), subsurface.instance.parent.clone()));
destroyed.push((subsurface.instance.wl_surface.clone(), subsurface.instance.parent.clone(), subsurface.id));
subsurface.instance.wl_surface.attach(None, 0, 0);
subsurface.instance.wl_surface.commit();
@ -1544,7 +1668,12 @@ impl SctkState {
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(subsurface.instance) )
);
}
for (destroyed, parent) in destroyed {
for (destroyed, parent, id) in destroyed {
if let Some(blurred) = self.blur_surfaces.remove(&id) {
blurred.destroy();
}
_ = self.corner_radii.remove(&id);
if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| {
f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed)
}).and_then(|f| Some((parent, &mut f.kbd_focus))) {
@ -1572,9 +1701,26 @@ impl SctkState {
}
Action::RoundedCorners(id, v) => {
if let Some(manager) = self.corner_radius_manager.as_ref() {
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
let geo_size: LogicalSize<f64> = w.window.surface_size().cast::<f64>().to_logical(w.window.scale_factor());
let half_min_dim = ((geo_size.width as u32).min(geo_size.height as u32) / 2);
enum Surface {
Xdg(XdgSurface, Option<XdgToplevel>),
Wlr(ZwlrLayerSurfaceV1),
}
let s = if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
Some((Surface::Xdg(w.xdg_surface(&self.connection), Some(w.xdg_toplevel(&self.connection))), w.window.surface_size().cast::<f64>().to_logical(w.window.scale_factor())))
} else if let Some(p) = self.popups.iter_mut().find(|w| w.data.id == id) {
let guard = p.common.lock().unwrap();
Some((Surface::Xdg(p.popup.xdg_surface().clone(), None), guard.size.cast::<f64>()))
} else if let Some(l) = self.layer_surfaces.iter_mut().find(|l| l.id == id) {
let guard = l.common.lock().unwrap();
match l.surface.kind() {
SurfaceKind::Wlr(l) => Some((Surface::Wlr(l.clone()), guard.size.cast::<f64>())),
_ => None
}
} else {
None
};
if let Some((s, geo_size)) = s {
let half_min_dim = (geo_size.width as u32).min(geo_size.height as u32) / 2;
if let Some(radii) = v {
let adjusted_radii = CornerRadius {
@ -1583,37 +1729,73 @@ impl SctkState {
bottom_right: radii.bottom_right.min(half_min_dim),
bottom_left: radii.bottom_left.min(half_min_dim),
};
if let Some((protocol_object, corner_radii)) = w.corner_radius.as_mut() {
if let Some((protocol_object, corner_radii)) = self.corner_radii.get_mut(&id) {
if *corner_radii != Some(adjusted_radii) {
protocol_object.0.0.set_radius(
match protocol_object.0.as_ref() {
CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.set_radius(
adjusted_radii.top_left,
adjusted_radii.top_right,
adjusted_radii.bottom_right,
adjusted_radii.bottom_left,
),
CornerRadiusWrapper::Wlr(protocol_object) => protocol_object.set_radius(
adjusted_radii.top_left,
adjusted_radii.top_right,
adjusted_radii.bottom_right,
adjusted_radii.bottom_left,
)
};
*corner_radii = Some(adjusted_radii.clone());
}
} else {
let protocol_object = match s {
Surface::Xdg(s, w) => {
if manager.version() == 1 {
if let Some(w) = w {
CornerRadiusWrapper::Xdg(manager.get_corner_radius(&w, &self.queue_handle, ()))
} else {
log::error!("Corner radius is not supported for popups on xdg shell v1");
return Ok(());
}
} else {
CornerRadiusWrapper::Xdg(manager.get_corner_radius_surface(&s, &self.queue_handle, ()))}
}
Surface::Wlr(l) => {
if manager.version() == 1 {
return Ok(());
}
CornerRadiusWrapper::Wlr(manager.get_corner_radius_layer(&l, &self.queue_handle, ()))}
};
match &protocol_object {
CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.set_radius(
adjusted_radii.top_left,
adjusted_radii.top_right,
adjusted_radii.bottom_right,
adjusted_radii.bottom_left,
);
*corner_radii = Some(adjusted_radii.clone());
}
} else {
let toplevel = w.xdg_toplevel(&self.connection);
let protocol_object = manager.get_corner_radius(&toplevel, &self.queue_handle, ());
protocol_object.set_radius(
adjusted_radii.top_left,
adjusted_radii.top_right,
adjusted_radii.bottom_right,
adjusted_radii.bottom_left,
);
w.corner_radius = Some((SctkCornerRadius(Arc::new(MyCosmicCornerRadiusToplevelV1( protocol_object))), Some(adjusted_radii.clone())));
),
CornerRadiusWrapper::Wlr(protocol_object) => protocol_object.set_radius(
adjusted_radii.top_left,
adjusted_radii.top_right,
adjusted_radii.bottom_right,
adjusted_radii.bottom_left,
)
};
_ = self.corner_radii.insert(id, (SctkCornerRadius(Arc::new(protocol_object)), Some(adjusted_radii.clone())));
}
} else {
if let Some(old) = w.corner_radius.as_mut() {
old.0.0.as_ref().0.unset_radius();
if let Some(old) = self.corner_radii.get_mut(&id) {
match old.0.0.as_ref() {
CornerRadiusWrapper::Xdg(protocol_object) => protocol_object.unset_radius(),
CornerRadiusWrapper::Wlr(protocol_object) => {
protocol_object.unset_radius()
}
};
old.1 = None;
}
}
} else {
if let Some(v) = v{
if let Some(v) = v {
_ = self.pending_corner_radius.insert(id, v);
} else {
_ = self.pending_corner_radius.remove(&id);
@ -1621,10 +1803,95 @@ impl SctkState {
}
}
}
Action::BlurSurface(id, rectangles) => {
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1;
if let Some(bg_effect_mgr) = self.ext_background_effect_manager.as_mut() && !bg_effect_mgr.capabilities().contains(ext_background_effect_manager_v1::Capability::Blur) {
bg_effect_mgr.enqueue(id, rectangles.clone());
return Ok(());
}
let s = if let Some(s) = self.popups.iter().find(|s| s.data.id == id) {
s.popup.wl_surface()
} else if let Some(s) = self.layer_surfaces.iter().find(|s| s.id == id) {
s.surface.wl_surface()
} else if let Some(s) = self.lock_surfaces.iter().find(|s| s.id == id) {
s.session_lock_surface.wl_surface()
} else if let Some(subsurface) = self.subsurfaces.iter().find(|s| s.id == id) {
&subsurface.instance.wl_surface
} else {
log::info!("Failed to find surface for blur action");
if let Some(rectangles) = rectangles {
_ = self.pending_blur.insert(id, rectangles.clone());
}
return Ok(());
}.clone();
self.apply_blur(id, rectangles, &s)
},
};
Ok(())
}
fn apply_blur(
&mut self,
id: core::window::Id,
rectangles: Option<Vec<Rectangle>>,
s: &WlSurface,
) {
let existing_blur = self.blur_surfaces.entry(id);
match (existing_blur, rectangles) {
(Entry::Occupied(occupied_entry), None) => {
let blur_surface = occupied_entry.remove();
blur_surface.destroy();
}
(Entry::Occupied(mut occupied_entry), Some(rectangles)) => {
let blur_surface = occupied_entry.get_mut();
let region = self
.compositor_state
.wl_compositor()
.create_region(&self.queue_handle, ());
for rect in rectangles.into_iter() {
region.add(
rect.x.round() as i32,
rect.y.round() as i32,
rect.width.round() as i32,
rect.height.round() as i32,
);
}
// update existing blur surfaces
blur_surface.set_blur_region(Some(&region));
}
(Entry::Vacant(..), None) => {
// nothing to remove
}
(Entry::Vacant(vacant_entry), Some(rectangles)) => {
if self.ext_background_effect_manager.is_none() {
log::error!("Blur effect is not supported.");
return;
}
let region = self
.compositor_state
.wl_compositor()
.create_region(&self.queue_handle, ());
for rect in rectangles {
region.add(
rect.x.round() as i32,
rect.y.round() as i32,
rect.width.round() as i32,
rect.height.round() as i32,
);
}
let blur_manager =
self.ext_background_effect_manager.as_mut().unwrap();
let blur_surface = blur_manager.blur(s, &self.queue_handle);
blur_surface.set_blur_region(Some(&region));
_ = vacant_entry.insert(blur_surface);
}
}
}
pub fn get_subsurface(
&mut self,
settings: SctkSubsurfaceSettings,
@ -1638,9 +1905,17 @@ impl SctkState {
),
SubsurfaceCreationError,
> {
let Some(subsurface_state) = self.subsurface_state.as_ref() else {
let Some(subsurface_state) = self.subsurface_state.clone() else {
return Err(SubsurfaceCreationError::Unsupported);
};
if self.layer_surfaces.iter().any(|s| s.id == settings.id)
|| self.windows.iter().any(|w| w.id == settings.id)
|| self.popups.iter().any(|p| p.data.id == settings.id)
|| self.subsurfaces.iter().any(|s| s.id == settings.id)
{
log::warn!("Subsurface with id {:?} already exists", settings.id);
return Err(SubsurfaceCreationError::AlreadyExists);
}
let size = settings.size.unwrap_or(Size::new(1., 1.));
let half_w = size.width / 2.;
@ -1746,7 +2021,9 @@ impl SctkState {
region.destroy();
}
wl_surface.commit();
if let Some(blur) = self.pending_blur.remove(&settings.id) {
_ = self.apply_blur(settings.id, Some(blur), &wl_surface);
}
let wp_viewport = subsurface_state.wp_viewporter.get_viewport(
&wl_surface,
@ -1822,7 +2099,11 @@ impl SctkState {
// XXX subsurfaces need to be sorted by z in descending order
self.subsurfaces
.sort_by(|a, b| b.instance.z.cmp(&a.instance.z));
if let Some(corners) = self.pending_corner_radius.remove(&id) {
self.handle_action(Action::RoundedCorners(id, Some(corners)));
}
wl_surface.commit();
Ok((
id,
parent.wl_surface().clone(),

View file

@ -0,0 +1,115 @@
use std::collections::HashMap;
use cctk::sctk;
use iced_futures::futures::SinkExt;
use iced_runtime::core::Rectangle;
use iced_runtime::platform_specific::wayland::Action;
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1::{Capability, Event, ExtBackgroundEffectManagerV1};
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
use crate::event_loop::state::SctkState;
use crate::{platform_specific, window};
#[derive(Debug, Clone)]
pub struct ExtBackgroundEffectManager {
manager: ExtBackgroundEffectManagerV1,
capabilities: Capability,
queued_blur_actions: HashMap<window::Id, Option<Vec<Rectangle>>>,
}
impl ExtBackgroundEffectManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<SctkState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self {
manager,
capabilities: Capability::empty(),
queued_blur_actions: HashMap::new(),
})
}
pub fn blur(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<SctkState>,
) -> ExtBackgroundEffectSurfaceV1 {
self.manager
.get_background_effect(surface, queue_handle, ())
}
pub fn enqueue(&mut self, id: window::Id, rects: Option<Vec<Rectangle>>) {
_ = self.queued_blur_actions.insert(id, rects);
}
pub fn capabilities(&self) -> Capability {
self.capabilities
}
}
impl Dispatch<ExtBackgroundEffectManagerV1, GlobalData, SctkState>
for ExtBackgroundEffectManager
{
fn event(
state: &mut SctkState,
_: &ExtBackgroundEffectManagerV1,
event: <ExtBackgroundEffectManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<SctkState>,
) {
match event {
Event::Capabilities { flags } => match flags {
wayland_client::WEnum::Value(capability) => {
let mut queued_actions = Vec::new();
if let Some(bg_effect_mgr) =
state.ext_background_effect_manager.as_mut()
{
bg_effect_mgr.capabilities = capability;
if capability.contains(Capability::Blur) {
queued_actions = bg_effect_mgr
.queued_blur_actions
.drain()
.collect();
state
.sctk_events
.push(crate::sctk_event::SctkEvent::Blur);
}
}
for (id, rects) in queued_actions {
_ = state.handle_action(Action::BlurSurface(id, rects));
}
}
wayland_client::WEnum::Unknown(u) => {
log::warn!("Unknown value: {u:?}");
}
},
e => {
log::warn!("Ignored event {e:?}");
}
}
}
}
impl Dispatch<ExtBackgroundEffectSurfaceV1, (), SctkState>
for ExtBackgroundEffectManager
{
fn event(
_: &mut SctkState,
_: &ExtBackgroundEffectSurfaceV1,
_: <ExtBackgroundEffectSurfaceV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<SctkState>,
) {
// There is no event
}
}
delegate_dispatch!(SctkState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager);
delegate_dispatch!(SctkState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager);

View file

@ -1,6 +1,7 @@
// handlers
pub mod activation;
pub mod compositor;
pub mod ext_background_effect;
pub mod output;
pub mod overlap;
pub mod seat;
@ -12,14 +13,14 @@ pub mod toplevel;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;
use cctk::{sctk::{
use cctk::sctk::{
delegate_registry, delegate_shm,
output::OutputState,
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::SeatState,
shm::{Shm, ShmHandler},
}};
};
use crate::platform_specific::wayland::event_loop::state::SctkState;

View file

@ -4,10 +4,46 @@ use crate::platform_specific::wayland::{
};
use cctk::sctk::{
delegate_keyboard,
seat::keyboard::{KeyboardHandler, Keysym},
seat::keyboard::{KeyboardHandler, Keysym, Modifiers},
};
use cctk::sctk::{reexports::client::Proxy, seat::keyboard::RawModifiers};
fn modifiers_from_keysyms(
keysyms: &[Keysym],
previous: Modifiers,
) -> Modifiers {
let mut modifiers = Modifiers {
caps_lock: previous.caps_lock,
num_lock: previous.num_lock,
..Modifiers::default()
};
for keysym in keysyms {
match keysym.raw() {
xkeysym::key::Shift_L | xkeysym::key::Shift_R => {
modifiers.shift = true
}
xkeysym::key::Control_L | xkeysym::key::Control_R => {
modifiers.ctrl = true
}
xkeysym::key::Alt_L | xkeysym::key::Alt_R => modifiers.alt = true,
xkeysym::key::Super_L
| xkeysym::key::Super_R
| xkeysym::key::Hyper_L
| xkeysym::key::Hyper_R
| xkeysym::key::Meta_L
| xkeysym::key::Meta_R => modifiers.logo = true,
xkeysym::key::Caps_Lock | xkeysym::key::Shift_Lock => {
modifiers.caps_lock = true
}
xkeysym::key::Num_Lock => modifiers.num_lock = true,
_ => {}
}
}
modifiers
}
impl KeyboardHandler for SctkState {
fn enter(
&mut self,
@ -17,9 +53,9 @@ impl KeyboardHandler for SctkState {
surface: &cctk::sctk::reexports::client::protocol::wl_surface::WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[Keysym],
keysyms: &[Keysym],
) {
let (i, mut is_active, seat) = {
let (i, mut is_active, seat, modifiers) = {
let (i, is_active, my_seat) =
match self.seats.iter_mut().enumerate().find_map(|(i, s)| {
if s.kbd.as_ref() == Some(keyboard) {
@ -41,9 +77,11 @@ impl KeyboardHandler for SctkState {
surface
};
_ = my_seat.kbd_focus.replace(surface.clone());
let modifiers = modifiers_from_keysyms(keysyms, my_seat._modifiers);
my_seat._modifiers = modifiers;
let seat = my_seat.seat.clone();
(i, is_active, seat)
(i, is_active, seat, modifiers)
};
if !is_active && self.seats[0].kbd_focus.is_none() {
@ -73,6 +111,12 @@ impl KeyboardHandler for SctkState {
seat_id: seat.clone(),
surface: surface.clone(),
});
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Modifiers(modifiers),
kbd_id: keyboard.clone(),
seat_id: seat.clone(),
surface: surface.clone(),
});
}
}
}
@ -126,6 +170,7 @@ impl KeyboardHandler for SctkState {
if self.windows.iter().any(|w| w.window.id() == id) {
continue;
}
let modifiers = s._modifiers;
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
@ -137,6 +182,12 @@ impl KeyboardHandler for SctkState {
kbd_id: s.kbd.clone().unwrap(),
seat_id: s.seat.clone(),
surface: surface.clone(),
});
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Modifiers(modifiers),
kbd_id: s.kbd.clone().unwrap(),
seat_id: s.seat.clone(),
surface: surface.clone(),
})
}
}
@ -248,6 +299,7 @@ impl KeyboardHandler for SctkState {
};
let seat_id = my_seat.seat.clone();
let kbd_id = keyboard.clone();
my_seat._modifiers = modifiers;
if is_active {
if let Some(surface) = my_seat.kbd_focus.clone() {

View file

@ -4,10 +4,9 @@ use crate::platform_specific::wayland::{
};
use cctk::sctk::{
delegate_seat,
reexports::client::{protocol::wl_keyboard::WlKeyboard, Proxy},
seat::{pointer::ThemeSpec, SeatHandler},
reexports::client::{Proxy, protocol::wl_keyboard::WlKeyboard},
seat::{SeatHandler, keyboard::Modifiers, pointer::ThemeSpec},
};
use iced_runtime::keyboard::Modifiers;
use std::sync::Arc;
impl SeatHandler for SctkState {

View file

@ -9,12 +9,12 @@ use crate::{
use cctk::sctk::{
delegate_touch,
reexports::client::{
protocol::{wl_surface::WlSurface, wl_touch::WlTouch},
Connection, Proxy, QueueHandle,
protocol::{wl_surface::WlSurface, wl_touch::WlTouch},
},
seat::touch::TouchHandler,
};
use iced_runtime::core::{touch, Point};
use iced_runtime::core::{Point, touch};
impl TouchHandler for SctkState {
fn down(

View file

@ -1,6 +1,9 @@
use crate::{
event_loop::state::CommonSurface,
platform_specific::wayland::{handlers::SctkState, sctk_event::SctkEvent},
platform_specific::wayland::{
event_loop::state::receive_frame, handlers::SctkState,
sctk_event::SctkEvent,
},
};
use cctk::sctk::{
delegate_session_lock,
@ -67,6 +70,10 @@ impl SessionLockHandler for SctkState {
configure,
first,
});
let wl_surface = session_lock_surface.wl_surface().clone();
receive_frame(&mut self.frame_status, &wl_surface);
self.request_redraw(&wl_surface);
}
}

View file

@ -1,17 +1,29 @@
use cctk::{sctk, cosmic_protocols::{
corner_radius::v1::client::{
use cctk::{
cosmic_protocols::corner_radius::v1::client::{
cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1,
cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
},
overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1,
}};
use sctk::reexports::{
client::{Connection, Dispatch, Proxy},
sctk,
};
use sctk::reexports::client::{Connection, Dispatch, Proxy};
use crate::event_loop::state::SctkState;
use crate::platform_specific::wayland::SctkEvent;
#[derive(Debug)]
pub enum CornerRadiusWrapper {
Xdg(CosmicCornerRadiusToplevelV1),
Wlr(CosmicCornerRadiusLayerV1),
}
impl Drop for CornerRadiusWrapper {
fn drop(&mut self) {
match self {
Self::Xdg(c) => c.destroy(),
Self::Wlr(c) => c.destroy(),
};
}
}
impl Dispatch<CosmicCornerRadiusManagerV1, ()> for SctkState {
fn event(
@ -21,15 +33,11 @@ impl Dispatch<CosmicCornerRadiusManagerV1, ()> for SctkState {
_data: &(),
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {}
) {
}
}
impl
Dispatch<
CosmicCornerRadiusToplevelV1,
(),
> for SctkState
{
impl Dispatch<CosmicCornerRadiusToplevelV1, ()> for SctkState {
fn event(
state: &mut Self,
_proxy: &CosmicCornerRadiusToplevelV1,
@ -39,7 +47,22 @@ impl
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {
match event {
_ => unimplemented!()
_ => unimplemented!(),
}
}
}
impl Dispatch<CosmicCornerRadiusLayerV1, ()> for SctkState {
fn event(
state: &mut Self,
_proxy: &CosmicCornerRadiusLayerV1,
event: <CosmicCornerRadiusLayerV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {
match event {
_ => unimplemented!(),
}
}
}

View file

@ -1,13 +1,13 @@
use crate::platform_specific::wayland::{
event_loop::state::{receive_frame, SctkState},
event_loop::state::{SctkState, receive_frame},
sctk_event::{LayerSurfaceEventVariant, SctkEvent},
};
use cctk::sctk::{
delegate_layer,
reexports::client::Proxy,
shell::{
wlr_layer::{Anchor, KeyboardInteractivity, LayerShellHandler},
WaylandSurface,
wlr_layer::{Anchor, KeyboardInteractivity, LayerShellHandler},
},
};
use std::fmt::Debug;

View file

@ -1,4 +1,4 @@
pub mod corner_radius;
pub mod layer;
pub mod xdg_popup;
pub mod xdg_window;
pub mod corner_radius;

View file

@ -69,6 +69,10 @@ impl Dispatch<ZwpTextInputV3, (), SctkState> for TextInputManager {
_conn: &Connection,
_qhandle: &QueueHandle<SctkState>,
) {
if cfg!(not(feature = "single-instance")) {
return;
}
let kbd_focus =
match state.seats.iter_mut().find_map(|s| s.kbd_focus.clone()) {
Some(surface) => surface,

View file

@ -1,10 +1,10 @@
//! Handling of the wp-viewporter.
use cctk::sctk::reexports::client::Dispatch;
use cctk::sctk::reexports::client::globals::{BindError, GlobalList};
use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface;
use cctk::sctk::reexports::client::Dispatch;
use cctk::sctk::reexports::client::{
delegate_dispatch, Connection, Proxy, QueueHandle,
Connection, Proxy, QueueHandle, delegate_dispatch,
};
use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter;

View file

@ -21,6 +21,7 @@ use iced_runtime::core::{Vector, window};
use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};
use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle};
use sctk_event::SctkEvent;
use std::sync::OnceLock;
use std::{collections::HashMap, sync::Arc};
use subsurface_widget::{SubsurfaceInstance, SubsurfaceState};
use wayland_backend::client::ObjectId;
@ -128,7 +129,6 @@ impl PlatformSpecific {
};
self.wayland.display_handle = Some(display);
self.wayland.proxy = Some(raw);
// TODO remove this
self.wayland.sender =
crate::platform_specific::event_loop::SctkEventLoop::new(
self.wayland.winit_event_sender.clone().unwrap(),

View file

@ -68,10 +68,8 @@ use cctk::{
xdg::{popup::PopupConfigure, window::WindowConfigure},
},
},
wayland_client::protocol::wl_subsurface::WlSubsurface,
};
use std::{
any::Any,
collections::HashMap,
num::NonZeroU32,
sync::{Arc, Mutex},
@ -98,9 +96,7 @@ use super::{
#[derive(Debug, Clone)]
pub enum SctkEvent {
//
// Input events
//
Blur,
SeatEvent {
variant: SeatEventVariant,
id: WlSeat,
@ -122,11 +118,6 @@ pub enum SctkEvent {
seat_id: WlSeat,
surface: WlSurface,
},
// TODO data device & touch
//
// Surface Events
//
WindowEvent {
variant: WindowEventVariant,
id: WlSurface,
@ -167,10 +158,6 @@ pub enum SctkEvent {
},
SubsurfaceEvent(SubsurfaceEventVariant),
//
// output events
//
NewOutput {
id: WlOutput,
info: Option<OutputInfo>,
@ -180,9 +167,7 @@ pub enum SctkEvent {
info: OutputInfo,
},
RemovedOutput(WlOutput),
//
// compositor events
//
ScaleFactorChanged {
factor: f64,
id: WlOutput,
@ -380,6 +365,12 @@ impl SctkEvent {
(variant.position.0, variant.position.1).into(),
)
}
events.push((
id.clone(),
iced_runtime::core::Event::Mouse(
mouse::Event::CursorEntered,
),
));
events.push((
id,
iced_runtime::core::Event::Mouse(
@ -1732,6 +1723,12 @@ impl SctkEvent {
),
),
)),
SctkEvent::Blur => events.push((
None,
iced_runtime::core::Event::PlatformSpecific(
PlatformSpecific::Wayland(wayland::Event::BlurEnabled),
),
)),
}
}
}

View file

@ -7,7 +7,6 @@ use crate::core::{
widget::{self, Widget},
};
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::HashMap,
fmt::Debug,
@ -410,7 +409,10 @@ impl SubsurfaceState {
.unwrap();
canvas[0..width as usize * height as usize * 4].copy_from_slice(data);
surface.damage_buffer(0, 0, width as i32, height as i32);
buffer.attach_to(&surface);
if let Err(err) = buffer.attach_to(&surface) {
log::warn!("failed to attach shm buffer to subsurface: {err}");
return;
}
surface.offset(offset.x as i32, offset.y as i32);
wp_viewport.set_destination(
(width as f64 / scale) as i32,

View file

@ -287,7 +287,21 @@ impl winit::window::Window for SctkWinitWindow {
}
fn set_blur(&self, blur: bool) {
// TODO
_ = self.tx.send(Action::Action(
iced_runtime::platform_specific::wayland::Action::BlurSurface(
self.id.inner(),
if blur {
Some(vec![iced_runtime::core::Rectangle {
x: 0.,
y: 0.,
width: f32::MAX,
height: f32::MAX,
}])
} else {
None
},
),
));
}
fn set_visible(&self, visible: bool) {}

View file

@ -335,19 +335,9 @@ where
}
if self.ime_state != Some((cursor, purpose)) {
// Specify only the bottom-left position of the cursor on Linux
// because fcitx5 doesn't work well with cursor areas of
// the top-left position and height.
// The candidate window hides the composing text (a.k.a. preedit).
let (cursor_y, cursor_height) =
if cfg!(not(any(target_os = "windows", target_os = "macos"))) {
(cursor.y + cursor.height, 0.0)
} else {
(cursor.y, cursor.height)
};
self.raw.set_ime_cursor_area(
LogicalPosition::new(cursor.x, cursor_y).into(),
LogicalSize::new(cursor.width, cursor_height).into(),
LogicalPosition::new(cursor.x, cursor.y).into(),
LogicalSize::new(cursor.width, cursor.height).into(),
);
self.raw.set_ime_purpose(conversion::ime_purpose(purpose));

View file

@ -97,6 +97,7 @@ where
self.ready = ready;
}
#[cfg(feature = "a11y")]
pub(crate) fn set_a11y_ready(&mut self, ready: bool) {
self.a11y_ready = ready;
}